volatileキーワードは、主に複数スレッド間でのデータ可視性を確保するために使用されます。
異なるスレッドが同じオブジェクトを操作する際、各スレッドはまずオブジェクトのコピーを自身の作業メモリに保持し、処理後にメインメモリに戻します。
複数スレッドが同時にオブジェクトを操作する場合、実際にはそれぞれがメインメモリから取得したコピーを操作しているため、同一オブジェクトを共有しているわけではありません。
volatile修飾子でマークされたフィールドは、可視性を保証します。このプロパティを使用する際、常にメインメモリとの同期が行われ、他のスレッドにも変更が通知されます。
volatileはオブジェクトプロパティの一貫性を保証しますが、アトミック性(原子性)は保証しません。あるスレッドが変更中でも、別のスレッドが既に状態を変更している可能性があります。
アリババグループの技術面接問題:
コンテナを用意し、スレッドAが10個の数値を追加し、スレッドBが監視して5個追加された時点で停止させる処理。
import java.util.List;
import java.util.ArrayList;
public class DataContainer {
public volatile List<Integer> dataList;
public DataContainer(){
dataList = new ArrayList<Integer>();
}
public void insertValue(int value){
dataList.add(value);
}
public int getCount(){
return dataList.size();
}
}
メインクラス:
public class MainClass {
public static void main(String[] args){
DataContainer container = new DataContainer();
Thread producer = new Thread(){
@Override
public void run() {
for(int index = 1; index <= 10; index++){
container.insertValue(index);
System.out.println(index + "が追加されました");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
};
Thread monitor = new Thread(){
@Override
public void run() {
while(true){
if(container.getCount() == 5){
try {
System.out.println("監視スレッド終了");
break;
} catch (Exception ex) {
ex.printStackTrace();
}
break;
}
}
}
};
producer.start();
monitor.start();
}
}
実行結果:
1が追加されました
2が追加されました
3が追加されました
4が追加されました
5が追加されました
監視スレッド終了
6が追加されました
7が追加されました
8が追加されました
9が追加されました
10が追加されました
条件を変えて、スレッドAが5個追加した時点で自身を停止させるように変更します。
Thread.stop()メソッドは非推奨になったため、現在では外部からのスレッド停止はフラグ変数を使用して制御します。外部からフラグを操作し、スレッド内部でそのフラグをチェックして停止します。
public class MainClass {
public static class ProducerThread extends Thread{
public volatile int controlFlag = 0;
}
public static void main(String[] args){
DataContainer container = new DataContainer();
ProducerThread producer = new ProducerThread(){
@Override
public void run() {
int counter = 0;
while(controlFlag == 0){
counter++;
container.insertValue(counter);
System.out.println(counter + "が追加されました");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
};
Thread controller = new Thread(){
@Override
public void run() {
while(true){
if(container.getCount() == 5){
try {
System.out.println("プロデューサースレッド停止");
producer.controlFlag = 1;
} catch (Exception ex) {
ex.printStackTrace();
}
break;
}
}
}
};
producer.start();
controller.start();
}
}
volatileキーワードを取り除いて実行すると、無限ループが発生します。これは、t1とt2が参照しているcontainerオブジェクトが実際には別々のコピーであり、お互いの変更を認識できないためです。