ExecutorService
はスレッドの安全性を保証しますか?
異なるスレッドから同じThreadPoolExecutorにジョブをサブミットしますが、タスクを相互作用/送信する前にエグゼキューターへのアクセスを同期する必要がありますか?
確かに、問題のJDKクラスは、スレッドセーフなタスク送信の明示的な保証を行っていないようです。ただし、実際には、ライブラリ内のすべてのExecutorService実装はこの方法でスレッドセーフです。これに依存するのは合理的だと思います。これらの機能を実装するすべてのコードはパブリックドメインに配置されているため、他の方法で完全に書き換える動機はまったくありません。
(他の回答に反して)スレッドセーフコントラクトは文書化されています:interface
javadocs(メソッドのjavadocとは対照的に)を参照してください。たとえば、 ExecutorService javadocの下部にあります。
メモリ一貫性の影響:RunnableまたはCallableタスクをExecutorServiceに送信する前のスレッド内のアクションhappen-beforeそのタスクによって実行されたアクションhappen-before結果はFuture.get()を介して取得されます。
これに答えるにはこれで十分です:
「タスクを操作/送信する前に、エグゼキューターへのアクセスを同期する必要がありますか?」
いいえ、そうではありません。外部の同期を行わずに(正しく実装された)ExecutorService
にジョブを構築して送信することは問題ありません。これは、主要な設計目標の1つです。
ExecutorService
はconcurrentユーティリティです。つまり、パフォーマンスのために、同期を必要とせずに最大限に動作するように設計されています。 (同期はスレッド競合を引き起こし、マルチスレッドの効率を低下させる可能性があります-特に多数のスレッドにスケールアップする場合)
タスクが将来いつ実行または完了するかについての保証はありません(サブミットしたスレッドと同じスレッドですぐに実行されることもあります)が、ワーカースレッドはサブミットスレッドが実行したすべての効果を確認できます投稿の時点まで。したがって、タスクは(実行するスレッド)同期、スレッドセーフクラス、または他の形式の「安全なパブリケーション」なしで、その使用のために作成されたデータを安全に読み取ることもできます。タスクを送信するという行為自体は、タスクへの入力データの「安全な公開」に十分です。タスクの実行中に入力データが変更されないようにする必要があります。
同様に、Future.get()
を介してタスクの結果をフェッチすると、取得スレッドは、エグゼキューターのワーカースレッドによって行われたすべての効果(返された結果と副作用の変更の両方で)を確認することが保証されますworker-threadが作成した可能性があります)。
また、この契約は、タスク自体がより多くのタスクを送信することは問題ないことを暗示しています。
「ExecutorServiceはスレッドの安全性を保証しますか?」
さて、質問のこの部分はもっと一般的です。たとえば、メソッドshutdownAndAwaitTermination
に関するスレッドセーフコントラクトのステートメントは見つかりませんでしたが、Javadocのコードサンプルでは同期を使用していないことに注意してください。 (おそらく、シャットダウンは、たとえばワーカースレッドではなく、エグゼキューターを作成したのと同じスレッドによって引き起こされるという隠れた仮定がありますか?)
ところで私は、並行プログラミングの世界についての優れた基礎資料として、「Java Concurrency In Practice」という本をお勧めします。
あなたの質問はかなり自由です:ExecutorService
インターフェースは、どこかのスレッドが送信されたRunnable
またはCallable
インスタンスを処理することを保証しています。
送信されたRunnable
/Callable
が、他のRunnable
/Callable
sインスタンスからアクセス可能な共有データ構造を参照している場合(異なるスレッドによって同時に処理される可能性があります)、このデータ構造全体でスレッドの安全性を確保するのはあなたの責任です。
質問の2番目の部分に答えるために、はい、タスクを送信する前にThreadPoolExecutorにアクセスできます。例えば.
BlockingQueue<Runnable> workQ = new LinkedBlockingQueue<Runnable>();
ExecutorService execService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.SECONDS, workQ);
...
execService.submit(new Callable(...));
[〜#〜] edit [〜#〜]
ブライアンのコメントに基づき、あなたの質問を誤解した場合:複数のプロデューサースレッドからExecutorService
へのタスクの送信は、通常、スレッドセーフになります(可能な限り、インターフェイスのAPIで明示的に言及されていないにもかかわらず)教えてください。スレッドセーフを提供しない実装は、マルチスレッド環境では役に立ちません(複数のプロデューサー/複数のコンシューマーはかなり一般的なパラダイムであるため)。これは特にExecutorService
(およびその他のJava.util.concurrent
)のために設計されました。
ThreadPoolExecutor
の答えは、単純にyesです。 ExecutorService
はnotを実行するか、すべての実装がスレッドセーフであることを保証します。これらのタイプのコントラクトはJavaインターフェイスの範囲外です。ただし、ThreadPoolExecutor
は両方ともスレッドセーフであることが明確に文書化されています。さらに、ThreadPoolExecutor
はJava.util.concurrent.BlockingQueue
すべての実装を要求するインターフェースはスレッドセーフです。BlockingQueue
のJava.util.concurrent.*
実装はすべてスレッドセーフであると安全に想定できます。非標準の実装はそうではありませんが、誰かが実装したBlockingQueue
実装キュースレッドセーフではありません。
タイトルの質問に対する答えは、明らかにyesです。質問の次の本文に対する答えはおそらくです。これは、両者の間に矛盾があるためです。
Luke Usherwood による答えとは反対に、ExecutorService
の実装がスレッドセーフであることが保証されていることはドキュメントには含まれていません。 ThreadPoolExecutor
の質問については、他の回答を参照してください。
はい、 happens-before 関係が指定されていますが、これは Miles 。 Luke Usherwood の答えでは、前者は後者を証明するのに十分であると述べられていますが、実際の議論はなされていません。
「スレッドセーフ」とはさまざまな意味がありますが、必要な happens-before 関係を簡単に満たすExecutor
(ExecutorService
ではなく、違いはありません)の単純な反例です。 count
フィールドへの非同期アクセスのため、スレッドセーフではありません。
class CountingDirectExecutor implements Executor {
private int count = 0;
public int getExecutedTaskCount() {
return count;
}
public void execute(Runnable command) {
command.run();
}
}
免責事項:私は専門家ではなく、自分で答えを探していたため、この質問を見つけました。
ThreadPoolExecutorの場合、送信はスレッドセーフです。ソースコードはjdk8で確認できます。新しいタスクを追加するとき、mainLockを使用してスレッドセーフを確保します。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}