Java三大性質總結:原子性、可見性以及有序性

本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,須要本身領取。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
java

1. 三大性質簡介
在併發編程中分析線程安全的問題時每每須要切入點,那就是兩大核心:JMM抽象內存模型以及happens-before規則Java內存模型以及happens-before規則,三條性質:原子性,有序性和可見性。關於synchronized和volatile已經討論過了,就想着將併發編程中這兩大神器在 原子性,有序性和可見性上作一個比較,固然這也是面試中的高頻考點,值得注意。面試


2. 原子性
原子性是指一個操做是不可中斷的,要麼所有執行成功要麼所有執行失敗,有着「同生共死」的感受。及時在多個線程一塊兒執行的時候,一個操做一旦開始,就不會被其餘線程所幹擾。咱們先來看看哪些是原子操做,哪些不是原子操做,有一個直觀的印象:編程

 int a = 10;  //1
 a++;  //2
 int b=a; //3
 a = a+1; //4


上面這四個語句中只有第1個語句是原子操做,將10賦值給線程工做內存的變量a,而語句2(a++),實際上包含了三個操做:1. 讀取變量a的值;2:對a進行加一的操做;3.將計算後的值再賦值給變量a,而這三個操做沒法構成原子操做。對語句3,4的分析同理可得這兩條語句不具有原子性。固然,java內存模型中定義了8中操做都是原子的,不可再分的。
安全

  1. lock(鎖定):做用於主內存中的變量,它把一個變量標識爲一個線程獨佔的狀態;性能優化

  2. unlock(解鎖):做用於主內存中的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定併發

  3. read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工做內存中,以便後面的load動做使用;app

  4. load(載入):做用於工做內存中的變量,它把read操做從主內存中獲得的變量值放入工做內存中的變量副本jvm

  5. use(使用):做用於工做內存中的變量,它把工做內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量的值的字節碼指令時將會執行這個操做;分佈式

  6. assign(賦值):做用於工做內存中的變量,它把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做;ide

  7. store(存儲):做用於工做內存的變量,它把工做內存中一個變量的值傳送給主內存中以便隨後的write操做使用;

  8. write(操做):做用於主內存的變量,它把store操做從工做內存中獲得的變量的值放入主內存的變量中。

上面的這些指令操做是至關底層的,能夠做爲擴展知識面掌握下。那麼如何理解這些指令了?好比,把一個變量從主內存中複製到工做內存中就須要執行read,load操做,將工做內存同步到主內存中就須要執行store,write操做。

注意的是:java內存模型只是要求上述兩個操做是順序執行的並非連續執行的。也就是說read和load之間能夠插入其餘指令,store和writer能夠插入其餘指令。好比對主內存中的a,b進行訪問就能夠出現這樣的操做順序:read a,read b, load b,load a


由原子性變量操做read,load,use,assign,store,write,能夠大體認爲基本數據類型的訪問讀寫具有原子性(例外就是long和double的非原子性協定)


synchronized
上面一共有八條原子操做,其中六條能夠知足基本數據類型的訪問讀寫具有原子性,還剩下lock和unlock兩條原子操做。若是咱們須要更大範圍的原子性操做就可使用lock和unlock原子操做。

儘管jvm沒有把lock和unlock開放給咱們使用,但jvm以更高層次的指令monitorenter和monitorexit指令開放給咱們使用,反應到java代碼中就是---synchronized關鍵字,也就是說synchronized知足原子性
volatile 咱們先來看這樣一個例子:

public class VolatileExample {
    private static volatile int counter = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++)
                        counter++;
                }
            });
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter);
    }
}


開啓10個線程,每一個線程都自加10000次,若是不出現線程安全的問題最終的結果應該就是:10*10000 = 100000;但是運行屢次都是小於100000的結果,問題在於 volatile並不能保證原子性,在前面說過counter++這並非一個原子操做,包含了三個步驟:

1.讀取變量counter的值;

2.對counter加一;

3.將新值賦值給變量counter。

若是線程A讀取counter到工做內存後,其餘線程對這個值已經作了自增操做後,那麼線程A的這個值天然而然就是一個過時的值,所以,總結果必然會是小於100000的。
若是讓volatile保證原子性,必須符合如下兩條規則:

  1. 運算結果並不依賴於變量的當前值,或者可以確保只有一個線程修改變量的值;

  2. 變量不須要與其餘的狀態變量共同參與不變約束

3. 有序性
synchronized
synchronized語義表示鎖在同一時刻只能由一個線程進行獲取,當鎖被佔用後,其餘線程只能等待。所以,synchronized語義就要求線程在訪問讀寫共享變量時只能「串行」執行,所以synchronized具備有序性


volatile
在java內存模型中說過,爲了性能優化,編譯器和處理器會進行指令重排序;也就是說java程序自然的有序性能夠總結爲:若是在本線程內觀察,全部的操做都是有序的;若是在一個線程觀察另外一個線程,全部的操做都是無序的。在單例模式的實現上有一種雙重檢驗鎖定的方式(Double-checked Locking)。

代碼以下:

public class Singleton {
    private Singleton() { }
    private volatile static Singleton instance;
    public Singleton getInstance(){
        if(instance==null){
            synchronized (Singleton.class){
                if(instance==null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}


這裏爲何要加volatile了?咱們先來分析一下不加volatile的狀況,有問題的語句是這條:
instance = new Singleton();

這條語句實際上包含了三個操做:

1.分配對象的內存空間;

2.初始化對象;

3.設置instance指向剛分配的內存地址。

但因爲存在重排序的問題,可能有如下的執行順序:

v2-6c67f47a8d82c2b862e4e92d583cb225_hd.jpg


若是2和3進行了重排序的話,線程B進行判斷if(instance==null)時就會爲true,而實際上這個instance並無初始化成功,顯而易見對線程B來講以後的操做就會是錯得。

用volatile修飾的話就能夠禁止2和3操做重排序,從而避免這種狀況。

volatile包含禁止指令重排序的語義,其具備有序性


4. 可見性
可見性是指當一個線程修改了共享變量後,其餘線程可以當即得知這個修改。經過以前對內存synchronzed語義進行了分析,當線程獲取鎖時會從主內存中獲取共享變量的最新值,釋放鎖的時候會將共享變量同步到主內存中。

從而,synchronized具備可見性。一樣的在volatile分析中,會經過在指令中添加lock指令,以實現內存可見性。所以, volatile具備可見性


5. 總結

經過這篇文章,主要是比較了synchronized和volatile在三條性質:原子性,可見性,以及有序性的狀況,

概括以下:
synchronized: 具備原子性,有序性和可見性; volatile:具備有序性和可見性

相關文章
相關標籤/搜索