在《Volatile 關鍵字(上)》一文中遺留了一個問題,就是 volatile 只修飾了 missedIt 變量,而沒修飾value 變量,但是在線程讀取 value 的值的時候,也讀到的是最新的數(shù)據。
下面講解問題出現(xiàn)的原因。
首先明確一點:假如有兩個線程分別讀寫 volatile 變量時,線程 A 寫入了某 volatile 變量,線程 B 在讀取該 volatile 變量時,便能看到線程 A 對該 volatile 變量的寫入操作,關鍵在這里,它不僅會看到對該 volatile 變量的寫入操作,A 線程在寫 volatile 變量之前所有可見的共享變量,在 B 線程讀同一個 volatile 變量后,都將立即變得對 B 線程可見。
回過頭來看文章中出現(xiàn)的問題,由于程序中 volatile 變量 missedIt 的寫入操作在 value 變量寫入操作之后,而且根據 volatile 規(guī)則,又不能重排序,因此,在線程 B 讀取由線程 A 改變后的 missedIt 之后,它之前的 value 變量在線程 A 的改變也對線程 B 變得可見了。
我們顛倒一下 value=50 和 missedIt=true 這兩行代碼試下,即 missedIt=true 在前,value=50 在后,這樣便會得到我們想要的結果:value 值的改變不會被看到。
這應該是 JDK1.2 之后對 volatile 規(guī)則做了一些修訂的結果。
修改后的代碼如下:
public class Volatile extends Object implements Runnable {
//value變量沒有被標記為volatile
private int value;
//missedIt變量被標記為volatile
private volatile boolean missedIt;
//creationTime不需要聲明為volatile,因為代碼執(zhí)行中它沒有發(fā)生變化
private long creationTime;
public Volatile() {
value = 10;
missedIt = false;
//獲取當前時間,亦即調用Volatile構造函數(shù)時的時間
creationTime = System.currentTimeMillis();
}
public void run() {
print("entering run()");
//循環(huán)檢查value的值是否不同
while ( value < 20 ) {
//如果missedIt的值被修改為true,則通過break退出循環(huán)
if ( missedIt ) {
//進入同步代碼塊前,將value的值賦給currValue
int currValue = value;
//在一個任意對象上執(zhí)行同步語句,目的是為了讓該線程在進入和離開同步代碼塊時,
//將該線程中的所有變量的私有拷貝與共享內存中的原始值進行比較,
//從而發(fā)現(xiàn)沒有用volatile標記的變量所發(fā)生的變化
Object lock = new Object();
synchronized ( lock ) {
//不做任何事
}
//離開同步代碼塊后,將此時value的值賦給valueAfterSync
int valueAfterSync = value;
print("in run() - see value=" + currValue +", but rumor has it that it changed!");
print("in run() - valueAfterSync=" + valueAfterSync);
break;
}
}
print("leaving run()");
}
public void workMethod() throws InterruptedException {
print("entering workMethod()");
print("in workMethod() - about to sleep for 2 seconds");
Thread.sleep(2000);
//僅在此改變value的值
missedIt = true;
// value = 50;
print("in workMethod() - just set value=" + value);
print("in workMethod() - about to sleep for 5 seconds");
Thread.sleep(5000);
//僅在此改變missedIt的值
// missedIt = true;
value = 50;
print("in workMethod() - just set missedIt=" + missedIt);
print("in workMethod() - about to sleep for 3 seconds");
Thread.sleep(3000);
print("leaving workMethod()");
}
/*
*該方法的功能是在要打印的msg信息前打印出程序執(zhí)行到此所化去的時間,以及打印msg的代碼所在的線程
*/
private void print(String msg) {
//使用java.text包的功能,可以簡化這個方法,但是這里沒有利用這一點
long interval = System.currentTimeMillis() - creationTime;
String tmpStr = " " + ( interval / 1000.0 ) + "000";
int pos = tmpStr.indexOf(".");
String secStr = tmpStr.substring(pos - 2, pos + 4);
String nameStr = " " + Thread.currentThread().getName();
nameStr = nameStr.substring(nameStr.length() - 8, nameStr.length());
System.out.println(secStr + " " + nameStr + ": " + msg);
}
public static void main(String[] args) {
try {
//通過該構造函數(shù)可以獲取實時時鐘的當前時間
Volatile vol = new Volatile();
//稍停100ms,以讓實時時鐘稍稍超前獲取時間,使print()中創(chuàng)建的消息打印的時間值大于0
Thread.sleep(100);
Thread t = new Thread(vol);
t.start();
//休眠100ms,讓剛剛啟動的線程有時間運行
Thread.sleep(100);
//workMethod方法在main線程中運行
vol.workMethod();
} catch ( InterruptedException x ) {
System.err.println("one of the sleeps was interrupted");
}
}
}
運行結果如下:
http://wiki.jikexueyuan.com/project/java-concurrency/images/modifyresults.jpg" alt="" />
很明顯,這其實并不符合使用 volatile 的第二個條件:該變量要沒有包含在具有其他變量的不變式中。因此,在這里使用 volatile 是不安全的。
附上一篇講述 volatile 關鍵字正確使用的很好的文章:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html