java lock

不少人都只知道鎖的一些概念,也能講出來一二三四,可是我在面試別人的時候,一問:講講java中的同步,可能就只能回答出來synchronized,單例場景等。爲了不這種尷尬,今天我將經過例子,帶你們逐步認識Java中的鎖與應用場景。只要認真讀完,我相信對各位無論是工做仍是面試,都會有比較大的幫助。java

大綱:程序員

1. 併發的特性
2. 鎖的分類
3. synchronized
4. volatile
5. Lock
6. ThreadLocal
7. Atmoic
8. Semaphore
9. 阻塞隊列
10. 死鎖
11. CountdownLatch
12.CyclicBarrier
 面試

前言:爲何要使用同步?算法

Java容許多線程併發控制,當多個線程同時操做一個可共享的資源變量時(如數據的增刪改查),將會致使數據不許確性,相互之間產生衝突。所以加入同步鎖,避免在該線程沒有完成工做以前,被其餘線程調用,從而保證該變量的惟一性和準確性。編程

 

1. 併發的特性數組

併發的特性爲:原子性,有序性和可見性。緩存

 

1.1 何爲原子性?
原子性指一個操做是不可中斷的,在多線程場景下,一個原子操做一旦開始,就不會被其餘線程干擾破壞。多線程

那麼在Java中,哪些操做是原子的呢?併發

對基本類型的操做,除了long和double以外
全部引用reference的賦值操做
java.concurrent.Atomic.* 包中全部類的一切操做ide

 

在32位機器的上,對long和double的操做是非原子性的,是由於long和double都佔8個字節,64位。

在32位的操做系統上對64位的數據讀寫須要分兩步完成,每一步取32位數據。這樣對double和long的賦值操做就會有問題:若是有兩個線程同時寫一個變量內存,一個進程寫低32位,而另外一個寫高32位,這樣將致使獲取的64位數據是失效的數據。所以須要使用volatile關鍵字來防止此類現象,volatile自己不保證獲取和設置操做的原子性,僅僅保持修改的可見性。可是java的內存模型保證聲明爲volatile的long和double變量的get和set操做是原子的(其餘操做不會,如+-*/,volatile下面的目錄會有講解)。

Tips:

因爲Java的跨平臺性,基本數據類型在32位和64位機器上佔用的字節數是同樣的,不用像C/C++那樣作平臺適配。

64位CPU擁有更大的尋址能力,最大支持到16GB內存,而32bit只支持4G內存 。64位CPU一次可提取64位數據,比32位提升了一倍,理論上性能會提高1倍。

 

1.2 何爲有序性?
有序性是指程序執行的順序按照代碼的前後順序執行。

在Java內存模型中,容許編譯器和處理器對指令進行重排序。在單線程場景下,重排序不會影響程序的執行,可是在多線程併發場景下,卻會影響程序執行的正確性。(例如:重排的時候某些賦值會被提早) 

在Java裏面,能夠經過volatile關鍵字來保證必定的"有序性"。另外能夠經過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每一個時刻是有一個線程執行同步代碼,至關因而讓線程順序執行同步代碼,天然就保證了有序性。

Tips: 

重排序是指編譯器和處理器爲了優化程序性能而對指令序列進行從新排序的一種手段。

在虛擬機層面,爲了儘量減小內存操做速度遠慢於CPU運行速度所帶來的CPU空置的影響,虛擬機會按照本身的一些規則將程序編寫順序打亂——即寫在後面的代碼在時間順序上可能會先執行,而寫在前面的代碼會後執行——以儘量充分地利用CPU。

int a = 1;
boolean flag = true;
假如不是a = 1的操做,而是a = new byte[1024*1024](分配1M空間),那麼它會運行地很慢,此時CPU是等待其執行結束呢,仍是先執行下面的語句flag=true呢?顯然,先執行flag=true能夠提早使用CPU,加快總體效率,固然這樣的前提是不會產生錯誤。

雖然這裏有兩種狀況:後面的代碼先於前面的代碼開始執行;前面的代碼先開始執行,但當效率較慢的時候,後面的代碼開始執行並先於前面的代碼執行結束。無論誰先開始,總以後面的代碼在一些狀況下存在先結束的可能。 


在硬件層面,CPU會將接收到的一批指令按照其規則重排序,一樣是基於CPU速度比緩存速度快的緣由,和上一點的目的相似,只是硬件處理的話,每次只能在接收到的有限指令範圍內重排序,而虛擬機能夠在更大層面、更多指令範圍內重排序。

 

1.3 何爲可見性?
可見性是指多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以立刻看到修改的值。

Java提供了volatile關鍵字來保證可見性,當一個共享變量被volatile修飾時,它會保證修改的值會當即被更新到主存,當有其餘線程須要讀取時,它會去內存中讀取新值。 
而普通的共享變量不能保證可見性,由於普通共享變量被修改以後,何時被寫入主存是不肯定的,當其餘線程去讀取時,此時內存中可能仍是原來的舊值,所以沒法保證可見性。  

另外,經過synchronized和Lock也可以保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖而後執行同步代碼,而且在釋放鎖以前會將對變量的修改刷新到主存當中,所以能夠保證可見性。

 

 

2. 鎖的分類 


公平鎖/非公平鎖
可重入鎖
獨享鎖/共享鎖
互斥鎖/讀寫鎖
樂觀鎖/悲觀鎖
分段鎖
偏向鎖/輕量級鎖/重量級鎖
自旋鎖

 

這些分類,並非全指鎖的狀態,有的是指鎖的特性或者鎖的設計。

 

2.1 公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。非公平鎖是指多個線程獲取鎖的順序不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取到鎖。

Java中的ReentrantLock,能夠經過構造器指定該鎖是否公平,默認是非公平的,非公平鎖的優點在於吞吐量比公平鎖大。而Java中的synchronized,是一種非公平鎖,它並不像ReentrantLock同樣,經過AQS來實現線程調度,因此沒有方法讓它變成公平鎖。

2.2 可重入鎖
可重入鎖又叫作遞歸鎖,指的是同一個線程在外層方法裏獲取到了某個鎖,進入到該方法裏的內層方法會自動獲取到該鎖,這樣能夠避免死鎖問題。

Java中的synchronized和ReentrantLock都是可重入鎖。

    public static void main(String[] args) {
        // 演示可重入鎖
        TestReentrantLock testReentrantLock = new TestReentrantLock();
        testReentrantLock.functionA();
    }
 
    private static class TestReentrantLock {
 
        public synchronized void functionA() {
            System.out.println("functionA");
            // 若是synchronized不是可重入鎖,將直接死鎖
            functionB();
        }
 
        public synchronized void functionB() {
            System.out.println("functionB");
        }
    }
代碼輸出:

functionA

functionB

 

2.2 獨享鎖/共享鎖
獨享鎖是指該鎖一次只能被一個線程持有,而共享鎖是指該鎖一次能夠被多個線程持有。

synchronized毫無疑問是獨享鎖,Lock類的實現ReentrantLock也是獨享鎖,而Lock類的另外一個實現ReadWriteLock,它的讀鎖是共享的(可讓多個線程同時持有,提升讀的效率),而它的寫鎖是獨享的。ReadWriteLock的讀寫,寫讀,寫寫的過程是互斥的(後面的Lock目錄會詳細講解)。

 

2.3 互斥鎖/讀寫鎖
上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。

互斥鎖在Java中的具體實現就是ReentrantLock,包括synchronized。
讀寫鎖在Java中的具體實現就是ReadWriteLock。

 

2.4 樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什麼類型的鎖,而是指看待併發同步的角度。

悲觀鎖認爲對於同一個數據的併發操做,必定是會發生修改,哪怕沒有修改,也會認爲修改。所以對於同一個數據的併發操做,悲觀鎖採起加鎖的形式。悲觀的認爲,不加鎖的併發操做必定會出問題。
樂觀鎖則認爲對於同一個數據的併發操做,是不會發生修改的。在更新數據的時候,會採用嘗試更新,不斷從新的方式更新數據。樂觀的認爲,不加鎖的併發操做是沒有事情的。

從上面的描述能夠看出,悲觀鎖適合寫操做很是多的場景,樂觀鎖適合讀操做很是多的場景,不加鎖會帶來大量的性能提高。

在Java中利用各類鎖,其實就是悲觀鎖的使用。

而樂觀鎖在Java中的使用,則是無鎖編程,經常採用的是CAS算法,典型的例子就是原子類(好比AtomicBoolean, AtomicInteger),經過CAS自旋實現原子操做的更新。

 

2.5 分段鎖
這是從鎖的設計來分的,細化鎖的粒度,而不是一有操做就鎖住整個對象。

舉兩個例子:

(1) JDK中的HashTable

    /**
     * Returns the number of keys in this hashtable.
     *
     * @return  the number of keys in this hashtable.
     */
    public synchronized int size() {
        return count;
    }
 
    /**
     * Tests if this hashtable maps no keys to values.
     *
     * @return  <code>true</code> if this hashtable maps no keys to values;
     *          <code>false</code> otherwise.
     */
    public synchronized boolean isEmpty() {
        return count == 0;
    }
HashTable的全部函數都是用synchronized,用的同一把鎖,就是當前的HashTable對象,可想而知它的效率能高到哪兒去。

(2) JDK中的ConcurrentHashMap

  /**
     * Stripped-down version of helper class used in previous version,
     * declared for the sake of serialization compatibility
     */
    static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }
ConcurrentHashMap中的分段鎖稱爲Segment,它即相似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每一個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當須要put元素的時候,並非對整個hashmap進行加鎖,而是先經過hashcode來知道他要放在那一個分段中,而後對這個分段進行加鎖,因此當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。

可是,在統計size的時候,可就是獲取hashmap全局信息的時候,就須要獲取全部的分段鎖才能統計。

分段鎖的設計目的是細化鎖的粒度,當操做不須要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操做。

Tips:

後面會單開一章節分析ConcurrentHashMap的原理

 

2.6 偏向鎖/輕量級鎖/重量級鎖
這三種鎖是從鎖的狀態來劃分的,並且是針對synchronized。

在Java 5經過引入鎖升級的機制來實現高效Synchronized,這三種鎖的狀態是經過對象監視器在對象頭中的字段來代表的。
偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖,下降獲取鎖的代價。
輕量級鎖是指當鎖是偏向鎖的時候,被另外一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其餘線程會經過自旋的形式嘗試獲取鎖,不會阻塞,提升性能。
重量級鎖是指當鎖爲輕量級鎖的時候,另外一個線程雖然是自旋,但自旋不會一直持續下去,當自旋必定次數的時候,尚未獲取到鎖,就會進入阻塞,該鎖膨脹爲重量級鎖。重量級鎖會讓其餘申請的線程進入阻塞,性能下降。

 

2.7 自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會當即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上下文切換的消耗,缺點是循環會消耗CPU。

    private static class SpinLock {
 
        private AtomicReference<Thread> sign = new AtomicReference<>();
 
        public void lock() {
            Thread current = Thread.currentThread();
            while (!sign.compareAndSet(null, current)) {
            }
        }
 
        public void unlock() {
            Thread current = Thread.currentThread();
            sign.compareAndSet(current, null);
        }
    }
 

 

3. synchronized

synchronized是用來控制線程同步用的,能夠用來修飾方法或者代碼塊。被synchronized修飾的方法或者代碼塊,在多線程場景下,不會被多個線程同時執行。

 

3.1 synchronized修飾方法
public synchronized void test01() {
    System.out.println("test01");
}
注意,synchronized鎖住的是對象而不是代碼段,上面代碼synchronized鎖住的就是TestSynchronized的this對象。

 

3.2 synchronized修飾代碼塊
public void test02() {
   synchronized (this) {
       System.out.println("test02");
   }
}
test02方法中就有synchronized代碼塊,此時鎖住的也是TestSynchronized的this對象。

 

3.3 自定義對象鎖
private final Object object = new Object();
 
public void test04() {
    synchronized (object) {
        System.out.println("test04");
    }
}
這種方式的好處相似ConcurrentHashMap,對不一樣的操做加不一樣的鎖,能夠提升多線程場景下的吞吐量。可是缺點是須要新建立鎖對象,形成必定的開銷。(JDK源碼中有大量這種實現方式)

 

3.4 靜態synchronized方法
public static synchronized void test03() {
    System.out.println("test03");
}
test03()是一個靜態synchronized函數,鎖住的是TestSynchronized類對象,稱爲類鎖,test01, test02鎖稱爲對象鎖。

類鎖和對象鎖不會相互影響,由於不是同一把鎖。

咱們來看看下面程序的執行結果:

    private static class TestSynchronized {
 
        public synchronized void test01() {
            System.out.println("test01 start");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test01 end");
        }
 
        public void test02() {
            synchronized (this) {
                System.out.println("test02 start");
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("test02 end");
            }
        }
 
        public static synchronized void test03() {
            System.out.println("test03 start");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test03 end");
        }
    }
 
    public static void main(String[] args) {
        final TestSynchronized testSynchronized = new TestSynchronized();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                testSynchronized.test01();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                testSynchronized.test02();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                TestSynchronized.test03();
            }
        }).start();
    }
輸出:

test01 start
test03 start
test01 end
test03 end
test02 start
test02 end

分析:test01和test02共用一把鎖,就是TestSynchronized的一個實例對象,而test03使用的則是TestSynchronized的類對象做爲鎖。因爲test01先獲取到對象鎖,而後休眠2s,因此test02必須等test01休眠執行完後才能拿到鎖,而test03卻沒有受到影響。

 

3.5 單例模式使用synchronized
public class Singleton {      
 
    private volatile static Singleton singleton;  
            
    private Singleton () {
    }      
 
    public static Singleton getSingleton() {      
        if (singleton == null) {                   
            synchronized (Singleton.class) {                    
                if (singleton == null) {                                       
                    singleton = new Singleton();                  
                }          
            }     
         }      
         return singleton;      
    }  
}
以上是最好的寫法,具體能夠參考這篇文章:https://blog.csdn.net/xiangjai/article/details/51753793

簡單來講:雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其爲雙重檢查鎖,由於會有兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內。爲何在同步塊內還要再檢驗一次?由於可能會有多個線程一塊兒進入同步塊外的 if,若是在同步塊內不進行二次檢驗的話就會生成多個實例了。

 

3.6 i++場景須要使用synchronized修飾嗎?
答案是確定的。i++不是原子操做,它其實分爲了三步,第一步:讀取i的值,第二步,i + 1,第三步:將 i + 1計算的值從新賦值給i。

來看一個例子,不加鎖的狀況:

    private static final class TestSynchronized {
 
        private int i;
 
        public void increase() {
            i++;
            System.out.println(Thread.currentThread().getName() + " increase i = " + i);
        }
    }
 
    public static void main(String[] args) {
        final TestSynchronized testSynchronized = new TestSynchronized();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    testSynchronized.increase();
                }
            }, "thread - " + i).start();
        }
    }
執行後輸出:
thread - 0 increase i = 2
thread - 3 increase i = 4
thread - 2 increase i = 3
thread - 1 increase i = 2
thread - 4 increase i = 5

很明顯結果亂了,不符合正常邏輯。

 

而後咱們在i++操做上加上同步:

public void increase() {
    synchronized (this) {
        i++;
    }
    System.out.println(Thread.currentThread().getName() + " increase i = " + i);
}
執行輸出:

thread - 0 increase i = 1
thread - 2 increase i = 3
thread - 1 increase i = 2
thread - 3 increase i = 4
thread - 4 increase i = 5

能夠看到,五個線程的結果分別是:1, 2, 3, 4, 5(順序不必定,依靠CPU調度)。

咱們再拓展下,何時讀須要加鎖?

好比我銀行轉帳,先獲取餘額,若是餘額 = 0,則轉帳失敗。這個時候,若是多線程場景下去拿餘額,而後再以餘額爲條件或者對餘額進行操做,那麼讀這個操做是須要加鎖的。

好比我想知道在對i++以前,i的值是多少:

    private static final class TestSynchronized {
 
        private int i;
 
        public void increase() {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + " i want know current i = " + i);
                i++;
                System.out.println(Thread.currentThread().getName() + " after increase i = " + i);
            }
        }
    }
 
    public static void main(String[] args) {
        final TestSynchronized testSynchronized = new TestSynchronized();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    testSynchronized.increase();
                }
            }, "thread - " + i).start();
        }
    }
這個時候,拿i的值也是須要加鎖的。

執行輸出:

thread - 0 i want know current i = 0
thread - 0 after increase i = 1
thread - 4 i want know current i = 1
thread - 4 after increase i = 2
thread - 3 i want know current i = 2
thread - 3 after increase i = 3
thread - 2 i want know current i = 3
thread - 2 after increase i = 4
thread - 1 i want know current i = 4
thread - 1 after increase i = 5

 

上面對i++的同步操做,除了使用synchronized,還可使用Lock,AtomicInteger。

 

synchronized總結:

1. 儘可能縮小鎖的範圍,只須要在必須加鎖的代碼範圍內加鎖;

2. 鎖的代碼內儘可能不要作耗時操做,避免其餘線程長時間等待鎖;

3. 只是讀取不須要加鎖;

4. 若是隻是讀操做,沒有寫操做,則能夠不用加鎖,此種情形下,變量加上final關鍵字;

5. 若是有寫操做,可是變量的寫操做跟當前的值無關聯,且與其餘的變量也無關聯,則可考慮變量加上volatile關鍵字,同時寫操做方法經過synchronized加鎖;

6. 若是有寫操做,且寫操做依賴變量的當前值(如:i++),則getXXX和寫操做方法都要經過synchronized加鎖。

 

 

4. volatile

前面這麼長的內容中,出現過volatile的身影,如今我來帶你們好好了解下這哥們。

併發的特性爲:原子性,有序性和可見性。

 

4.1 volatile的定義
 

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾以後,那麼就具有了兩層語義:

1)保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。

2)禁止進行指令重排序。

先看一段代碼,假如線程1先執行,線程2後執行:

    private static final class TestVolatile {
 
        private boolean stop = false;
 
        public void testVolatile01() {
            while (!stop) {
                System.out.println("testVolatile01...");
            }
        }
 
        public void testVolatile02() {
            stop = true;
        }
    }
 
    public static void main(String[] args) {
        final TestVolatile testVolatile = new TestVolatile();
 
        Thread thread0 = new Thread(new Runnable() {
            @Override
            public void run() {
                testVolatile.testVolatile01();
            }
        });
        thread0.start();
 
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                testVolatile.testVolatile02();
            }
        });
        thread1.start();
    }
執行輸出:

testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...

能夠看到能夠中止掉線程執行,可是stop不會裏面寫入到主存,因此執行邏輯有延遲。

private volatile boolean stop = false;
stop標記加上volatile修飾,再執行:

testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
testVolatile01...
 

很明顯。

第一:使用volatile關鍵字會強制將修改的值當即寫入主存;

第二:使用volatile關鍵字的話,當線程2進行修改時,會致使線程1的工做內存中緩存變量stop的緩存行無效(反映到硬件層的話,就是CPU的L1或者L2緩存中對應的緩存行無效);

第三:因爲線程1的工做內存中緩存變量stop的緩存行無效,因此線程1再次讀取變量stop的值時會去主存讀取。

那麼在線程2修改stop值時(固然這裏包括2個操做,修改線程2工做內存中的值,而後將修改後的值寫入內存),會使得線程1的工做內存中緩存變量stop的緩存行無效,而後線程1讀取時,發現本身的緩存行無效,它會等待緩存行對應的主存地址被更新以後,而後去對應的主存讀取最新的值。

那麼線程1讀取到的就是最新的正確的值。

 

4.2 volatile保證原子性嗎?
從上面知道volatile關鍵字保證了操做的可見性,可是volatile能保證對變量的操做是原子性嗎?

    private static final class TestVolatile2 {
 
        private volatile int count;
 
        public void increase() {
            count++;
        }
    }
 
    public static void main(String[] args) {
        final TestVolatile2 testVolatile2 = new TestVolatile2();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        testVolatile2.increase();
                    countDownLatch.countDown();
                }
            }.start();
        }
        // 等待全部線程都執行完
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testVolatile2.count);
    }
預期結果應該是輸出10000,可是最後每次容許輸出的結果值都小於10000。

根源就是自增操做不是原子性操做,並且volatile也沒法保證對變量的任何操做都是原子性的。

修改爲這樣就好了:

public void increase() {
    synchronized(this) {
        count++;
    }
}
那若是是原子操做呢?答案是原子操做不須要加鎖,可是在32位機器的上,對long和double的操做是非原子性的,具體看前面講的原子性概念。

private static final class TestVolatile2 {
 
        private volatile int count;
 
        public void setCount(int count) {
            this.count = count;
        }
    }
 
    public static void main(String[] args) {
        final TestVolatile2 testVolatile2 = new TestVolatile2();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        testVolatile2.setCount(temp * j);
                    countDownLatch.countDown();
                }
            }.start();
        }
        // 等待全部線程都執行完
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testVolatile2.count);
    }
輸出的結果一直是:8991

 

總結下volatile的使用場景:

1. 狀態標記量。

2. double check。(如單例模式)

3. 獨立觀察。

4. volatile bean 模式。

前兩個用的比較多,具體的能夠參考:https://blog.csdn.net/mbmispig/article/details/79255959 ---------------------  做者:況衆文  來源:CSDN  原文:https://blog.csdn.net/u014294681/article/details/85239733  版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索