鍍金池/ 教程/ Java/ Volatile 關鍵字(下)
并發(fā)新特性—信號量 Semaphore
線程間協(xié)作:wait、notify、notifyAll
notify 通知的遺漏
notifyAll 造成的早期通知問題
多線程的實現(xiàn)方法
深入 Java 內存模型(1)
多線程環(huán)境下安全使用集合 API
并發(fā)新特性—Lock 鎖與條件變量
生產者—消費者模型
深入 Java 內存模型(2)
線程中斷
Volatile 關鍵字(上)
并發(fā)新特性—阻塞隊列與阻塞棧
可重入內置鎖
守護線程與線程阻塞
并發(fā)新特性—障礙器 CyclicBarrier
Volatile 關鍵字(下)
synchronized 關鍵字
synchronized 的另個一重要作用:內存可見性
并發(fā)新特性—Executor 框架與線程池
并發(fā)性與多線程介紹
死鎖
實現(xiàn)內存可見性的兩種方法比較:synchronized 和 Volatile
線程掛起、恢復與終止

Volatile 關鍵字(下)

在《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