並發編程之原子性、可見性、有序性的簡單理解


並發程序正確地執行,必須要保證原子性、可見性以及有序性。只要有一個沒有被保證,就有可能會導致程序運行不正確。

原子性:一個操作或多個操作要么全部執行完成且執行過程不被中斷,要么就不執行。

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

有序性:程序執行的順序按照代碼的先后順序執行。

對於單線程,在執行代碼時jvm會進行指令重排序,處理器為了提高效率,可以對輸入代碼進行優化,它不保證程序中各個語句的執行先后順序同代碼中的順序一致,但是它會保證保存最終執行結果和代碼順序執行的結果是一致的。

Java語言對原子性、可見性、有序性的保證

1、原子性

Java中,對基本數據類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷,要么執行,要么不執行。

X=10;  //原子性(簡單的讀取、將數字賦值給變量)

Y = x;  //變量之間的相互賦值,不是原子操作

X++;  //對變量進行計算操作

X = x+1;

語句2實際包括兩個操作,它先要去讀取x的值,再將y值寫入,兩個操作分開是原子性的。合在一起就不是原子性的。

語句3、4:x++  x=x+1包括3個操作:讀取x的值,x+1,將x寫入

注:可以通過 synchronized和Lock實現原子性。因為synchronized和Lock能夠保證任一時刻只有一個線程訪問該代碼塊。

 

2、可見性

Java提供了volatile關鍵字保證可見性。

當一個共享變量被volatile修飾時,它會保證修改的值立即被其他的線程看到,即修改的值立即更新到主存中,當其他線程需要讀取時,它會去內存中讀取新值。

Synchronized和Lock也可以保證可見性,因為它們可以保證任一時刻只有一個線程能訪問共享資源,並在其釋放鎖之前將修改的變量刷新到內存中,

 

3、有序性

在Java里面,可以通過volatile關鍵字來保證一定的“有序性”(具體原理在下一節講述volatile關鍵字)。另外可以通過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每個時刻是有一個線程執行同步代碼,相當於是讓線程順序執行同步代碼,自然就保證了有序性。

 

Java內存模型:每個線程都有自己的工作內存(類似於前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。

 

Java內存模型具備一些先天的“有序性”,即不需要通過任何手段就能夠得到保證的有序性,這個通常也稱為happens-before 原則。如果兩個操作的執行次序無法從happens-before原則推導出來,那么它們就不能保證它們的有序性,虛擬機可以隨意地對它們進行重排序。

 

指令重排序:java語言規范規定JVM線程內部維持順序化語義。即只要程序的最終結果與它順序化情況的結果相等,那么指令的執行順序可以與代碼順序不一致,此過程叫指令的重排序。

指令重排序的意義:JVM能根據處理器特性(CPU多級緩存系統、多核處理器等)適當的對機器指令進行重排序,使機器指令能更符合CPU的執行特性,最大限度的發揮機器性能。

下面就來具體介紹下happens-before原則(先行發生原則):

程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在后面的操作

鎖定規則:一個unLock操作先行發生於后面對同一個鎖額lock操作

volatile變量規則:對一個變量的寫操作先行發生於后面對這個變量的讀操作。如果一個線程先去寫一個變量,然后一個線程去進行讀取,那么寫入操作肯定會先行發生於讀操作。

傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C。

線程啟動規則:Thread對象的start()方法先行發生於此線程的每個一個動作

線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生

線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行

對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM