[Java] スレッド開始前後の共有変数

2017年9月4日月曜日

Java マルチスレッド

[Java][C]JavaのvolatileとCのvolatileでJavaのvolatileについて書いた。

けど、以下のようなコードでエンクロージングクラスからキャプチャされたローカル変数が起動された側のスレッドから見えることや、起動されたスレッドが設定した変数がスレッド終了を待機した側のスレッドから見えることが保証されているのか、実はあまりよくわかっていなかった。
        Counter counter = new Counter();
        
        IntStream.rangeClosed(1, 100).parallel()
            .forEach(x -> counter.increment());
            
        System.out.println(counter.getCount());

synchronizedを宣言している箇所や明示的に同期化機構を使っている箇所はわかりやすいけど、スレッドの開始時や終了時ってどうなんだろう。。。
改めて、この辺りについて、Javaの仕様を確認してみた。

Java言語仕様

この辺りの話は
The Java® Language Specification
17.4.5 Happens-before Order
に記載されている。

キャプチャされたローカル変数

スレッド起動時にキャプチャされたローカル変数に値が設定されていることは以下から保証される。

  • A call to start() on a thread happens-before any actions in the started thread.

スレッドのstartメソッドの呼び出しは起動されたスレッド内の処理よりも必ず先であるという順序関係が保証されるということだね。

スレッド内で設定した値のスレッド終了後の扱い

スレッド内で設定した値がスレッド終了後に値が設定されていることは以下から保証される。

  • All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

スレッドがjoinから復帰する前にスレッド内の処理は全て実行されているという順序関係が保証されるということだね。

スレッドプールではどうか?

確かに、Javaの言語仕様によって、スレッドの実行前後のアクションの前後関係は保証されるけど、スレッドプールで並列処理されるタスクはどうなってしまうんだろう。
スレッドプールは通常、タスクとスレッドが1対1の関係ではないので、スレッド起動後にタスクが実行されたり、タスク終了後にもスレッドが生存し続けてるなんてことは当たり前にある。

この場合は、Javaの言語仕様ではなく、java.util.concurrentパッケージがタスクの実行前後の関係を保証してくれる。
もちろん、concurrentを正しく使っていれば、だけど。

パッケージ java.util.concurrent メモリー整合性特性
java.util.concurrentとそのサブパッケージ内のすべてのクラスのメソッドは、これらの保証をより高いレベルの同期にまで拡張します。

これで、安心して無名クラスやラムダ式をかけるね!