起動しているスレッドを別スレッドから停止させようとするとき、次のような形のコードを時々見る。
import java.util.concurrent.TimeUnit; public class StopThread { private static boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(new Runnable() { public void run() { int i = 0; while ( !stopRequested ) { i++; } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested = true; } }
実はこれ、Effective Java に載っている悪い例である。実際にこれを実行すると、プログラムは無限ループに陥り停止しない。
通常、あるスレッドが変数に対して行った変更が他のスレッドから参照できる保証はない。上記の例では、メインスレッドが行った stopRequested の変更はバックグラウンドスレッドには見えておらず、結果としてバックグラウンドスレッドの持つ while 文の条件は常に true となり無限ループとなってしまう。これは、いわゆる Java メモリ・モデル (JMM) の可視性に関する問題である。
上記の例を正しく動かそうと思ったら、synchronized を使う必要がある。
import java.util.concurrent.TimeUnit; public class StopThread2 { private static boolean stopRequested; private static synchronized void requestStop() { stopRequested = true; } private static synchronized boolean stopRequested() { return stopRequested; } public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(new Runnable() { public void run() { int i = 0; while ( !stopRequested() ) { i++; } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); requestStop(); } }
synchronized を使うことで、メインスレッド側の stopRequested の変更がバックグラウンドスレッドに見えるようになることが保証される。
同期化は、同期化ブロックから出るときにはキャッシュがクリアされ、同期化ブロックに入るときにはキャッシュが無効化されることを保証するのです。こうすることで、あるモニターに保護された同期化ブロック期間中に一つのスレッドが書く値は、同じモニターに保護された同期化ブロックを実行している他のどのスレッドからも見えるのです。同期化はまた、コンパイラーが命令を同期化ブロック内から外へ移動することはない、ということも保証します(同期化ブロック外から内部への命令の移動は、場合によってはできますが)。
GitHub - IBM/japan-technology: IBM Related Japanese technical documents - Code Patterns, Learning Path, Tutorials, etc.