1. 程式人生 > >volatile 關鍵字(詳解)

volatile 關鍵字(詳解)

volatile 關鍵字

說明volatile 關鍵字之前,首先先簡單介紹下java記憶體模型,因為後續的介紹與java的記憶體模型息息相關。

詳細的就不說明了,百度上都有,簡單的說下。

Java 記憶體模型中的可見性原子性有序性

可見性: 可見性,是指執行緒之間的可見性,一個執行緒修改的狀態對另一個執行緒是可見的。

原子性:   原子是世界上的最小單位,具有不可分割性。

有序性: 即程式執行的順序按照程式碼的先後順序執行。


volatile,從字面上說是易變的、不穩定的,事實上,也確實如此,這個關鍵字的作用就是告訴編譯器,

只要是被此關鍵字修飾的變數都是易變的、不穩定的

volatile關鍵字的兩個特點:

(1)、記憶體可見性,即執行緒A對volatile變數的修改,其他執行緒獲取的volatile變數都是最新的。

(2)、可以禁止指令重排序

首先說下第一個volatile特點,假如不加這個關鍵字會怎麼樣,如:

public class test{
    private int key;
    public int getKey(){
        return key;
    }
    public void setKey(int key){
        this.key= key;
    }
}

在這個程式碼中,test不是執行緒安全的,因為key的set/get方法都是在沒有同步的執行緒中執行的,假如說有兩個執行緒同步執行,

執行緒1執行了set方法,執行緒2的get方法有可能取到值也有可能取不到值,解決辦法,就是在成員變數那塊加上volatile關鍵字,這樣的話,利用volatile關鍵字第一個特性,當執行緒1執行了set方法的時候,執行緒2的get方法也可以取到值。

以下有個列子,

說明了volatile保證不了原子性這個特點:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
       /**
        *   1)保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
       *     2)禁止進行指令重排序。
        * **/
    public  volatile int inc = 0;  //沒有達到預期原因是volatile關鍵字保證了java記憶體模型中的可讀性,但遞增操作保證不了原子性
    public   void increaseVolatile() {
        inc++;
    }      
    public static void main(String[] args) {
        final test4 test = new test4();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++){
                        test.increaseVolatile();
                    }
                };
            }.start();
        }
        
        while(Thread.activeCount()>1)  //保證前面的執行緒都執行完
            Thread.yield();
        System.out.println("volatile------"+test.inc);
               /**
         * 結論:
         * volatile關鍵字可以保證目標的可讀性,但是保證不了目標的原子性
         *
         * **/     
     }
}

通過上面的這段程式碼可以猜出,正常來說它的值應該等於10000,但是執行出的結果卻是可能等於10000,也有可能小於10000

如何解決,可以採用synchronized關鍵字達到預期目的,或者採用Lock達到預期目的,再或者採用AtomicInteger達到預期目的,下面是個對比的列子可以看成視覺化的結果:


import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
       /**
        *   1)保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
       *     2)禁止進行指令重排序。
        * **/
    public  volatile int inc = 0;  //沒有達到預期原因是volatile關鍵字保證了java記憶體模型中的可讀性,但遞增操作保證不了原子性
    public  int incc = 0;  //採用synchronized關鍵字達到預期目的
    public  int inccc = 0;  //採用Lock達到預期目的
    Lock lock = new ReentrantLock();
    public  AtomicInteger incccc = new AtomicInteger(); //採用AtomicInteger達到預期目的
    public synchronized  static  void increaseSynchronized() { //這裡包含相關類鎖和物件鎖的區別的知識,當有多個執行緒共同應                                                               //用的時候,使用類鎖。
        incc++;
    }
    public   void increaseVolatile() {
        inc++;
    }
    public   void increaseLock() {
        lock.lock();
         try { 
             inccc++;
         } finally{
             lock.unlock();
         }
    }
    public   void increaseAtomicInteger() {
        incccc.getAndIncrement();
    }       
    public static void main(String[] args) {
        final test4 test = new test4();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++){
                        test.increaseSynchronized();
                        test.increaseVolatile();
                        test.increaseLock();
                        test.increaseAtomicInteger();
                    }
                };
            }.start();
        }
        
        while(Thread.activeCount()>1)  //保證前面的執行緒都執行完
            Thread.yield();
        System.out.println("volatile------"+test.inc);
        System.out.println("synchronized-------"+test.incc);
        System.out.println("Lock-------"+test.inccc);
        System.out.println("AtomicInteger--------"+test.incccc);
        /**
         * 結論:
         * volatile關鍵字可以保證目標的可讀性,但是保證不了目標的原子性
         *
         * **/            
    }
}

效果:


volatile關鍵字的第二個特性應用場景:

1.狀態標記量

     /**
     * volatile關鍵字的應用場景(利用它的有序列性)
     * 1.狀態標記量
     * 2.double check 雙重檢查
     * **/
    volatile boolean flag = false;
    public void volatileYy(){
       while(!flag){
          //.....
       }
    }
    public void setFlag() {
        flag = true;
    }

 2.double check 雙重檢查