本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并發分布式等教程,一共30G,需要自己領取。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
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中操作都是原子的,不可再分的。
lock(鎖定):作用于主內存中的變量,它把一個變量標識為一個線程獨占的狀態;
unlock(解鎖):作用于主內存中的變量,它把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定
read(讀?。鹤饔糜谥鲀却娴淖兞?,它把一個變量的值從主內存傳輸到線程的工作內存中,以便后面的load動作使用;
load(載入):作用于工作內存中的變量,它把read操作從主內存中得到的變量值放入工作內存中的變量副本
use(使用):作用于工作內存中的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時將會執行這個操作;
assign(賦值):作用于工作內存中的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作;
store(存儲):作用于工作內存的變量,它把工作內存中一個變量的值傳送給主內存中以便隨后的write操作使用;
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保證原子性,必須符合以下兩條規則:
運算結果并不依賴于變量的當前值,或者能夠確保只有一個線程修改變量的值;
變量不需要與其他的狀態變量共同參與不變約束
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指向剛分配的內存地址。
但由于存在重排序的問題,可能有以下的執行順序:
如果2和3進行了重排序的話,線程B進行判斷if(instance==null)時就會為true,而實際上這個instance并沒有初始化成功,顯而易見對線程B來說之后的操作就會是錯得。
而用volatile修飾的話就可以禁止2和3操作重排序,從而避免這種情況。
volatile包含禁止指令重排序的語義,其具有有序性。
4. 可見性
可見性是指當一個線程修改了共享變量后,其他線程能夠立即得知這個修改。通過之前對內存synchronzed語義進行了分析,當線程獲取鎖時會從主內存中獲取共享變量的最新值,釋放鎖的時候會將共享變量同步到主內存中。
從而,synchronized具有可見性。同樣的在volatile分析中,會通過在指令中添加lock指令,以實現內存可見性。因此,?volatile具有可見性
5. 總結
通過這篇文章,主要是比較了synchronized和volatile在三條性質:原子性,可見性,以及有序性的情況,
歸納如下:
synchronized: 具有原子性,有序性和可見性;?volatile:具有有序性和可見性
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。