天天看點

線程---并發程式設計中的三個概念:原子性、有序性、可見性

原子性、有序性、可見性

  在并發程式設計中,我們通常會遇到以下三個問題:原子性問題,可見性問題,有序性問題。我們先看具體看一下這三個概念:

1. 原子性

原子性:即一個操作或者多個操作,要麼全部執行并且執行的過程不會被任何因素打斷,要麼就都不執行。

一個很經典的例子就是銀行賬戶轉賬問題:

  比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。

  試想如果這個操作不具備原子性,會造成什麼樣的後果。假如從賬戶A減去1000元之後,操作突然中止。然後又從賬戶B取出了500元,取出500元之後,再執行往賬戶B添加1000元的操作。

  不具備原子性的結果就是,導緻賬戶A雖然減去了1000元,但是賬戶B沒有收到這個轉過來的1000元。是以這個操作必須要具備原子性才能保證不出現一些意外的問題。

2. 可見性

可見性:指當多個線程通路同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

舉一個簡單的例子:

線程1
boolean flag = false;
flag = true;

線程2 
boolean signal;
signal = flag;
           

假設線程先開始執行,擷取flag的值為false存入線程1 的緩存中,存入主存,之後又将flag賦為true,再将true更改到線程1 的緩存中,并沒有立即存入主存中;

線程2開始執行signal = flag,會先去主存中讀取flag的值,并加載到線程2 的緩存中,此時signal讀到的flag的值為false而不是true。這就是可見性,線程1對變量做了修改,線程2 沒有立即看到修改後的值。

3.有序性

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

舉例:

int i = 0;              
boolean flag = false;
i = 1;                //語句1  
flag = true;          //語句2
           

上面代碼定義了一個int型變量,定義了一個boolean類型變量,然後分别對兩個變量進行指派操作。從代碼順序上看,語句1是在語句2前面的,那麼JVM在真正執行這段代碼的時候會保證語句1一定會在語句2前面執行嗎?不一定,為什麼呢?

 這裡可能會發生指令重排序(Instruction Reorder)。

什麼是指令重排序??

一般來說,處理器為了提高程式運作效率,可能會對輸入代碼進行優化,它不保證程式中各個語句的執行先後順序同代碼中的順序一緻,但是它會保證程式最終執行結果和代碼順序執行的結果是一緻的。

比如上面的代碼中,語句1和語句2誰先執行對最終的程式結果并沒有影響,那麼就有可能在執行過程中,語句2先執行而語句1後執行。

但是要注意,雖然處理器會對指令進行重排序,但是它會保證程式最終結果會和代碼順序執行結果相同,那麼它靠什麼保證的呢?再看下面一個例子:

int a = 10;    //語句1
int b = 5;    //語句2
a = a + 10;    //語句3
b = a * a;     //語句4
           

這段代碼的執行順序是:語句1====》語句2====》語句3 ====》 語句4

但是有沒有可能執行順序是這樣的:2-----1-------4-------3

答案是不會的,處理器在進行重排序時是會考慮指令之間的資料依賴性,如果一個指令2必須用到1的結果,那麼處理器會保證1會在2之前執行的。

指令重排序不會影響單個線程的執行,但是會影響到線程并發執行的正确性。