Java併發編程之CAS二源碼追根溯源

Java併發編程之CAS二源碼追根溯源java

在上一篇文章中,咱們知道了什麼是CAS以及CAS的執行流程,在本篇文章中,咱們將跟着源碼一步一步的查看CAS最底層實現原理。編程

本篇是《凱哥(凱哥Java:kagejava)併發編程學習》系列之《CAS系列》教程的第二篇:從源碼追根溯源查看CAS最底層是怎麼實現的。安全

本文主要內容:CAS追根溯源,完全找到CAS的根在哪裏。併發

一:查看AtomicInteger.compareAndSet源碼

經過上一篇文章學習,咱們知道了AtomicInteger.compareAndSet方法不加鎖能夠保證原子性(其原理就是unsafe+cas實現的),咱們來看看其源碼:ide

思考1:變量可見性

AtomicInteger對象(下文凱哥簡稱:atoInteger)怎麼保證變量內存可見性呢?學習

查看源碼:this

0fNpolejLXs


思考2:爲何上一篇13行的i.compareAndSet(1,1024)是false

咱們來看看atoInteger的compareAndSet方法。凱哥在上面添加了註釋。spa

0fNpom4CXM8


在調用unsafe的compareAndSwapInt這個方法的時候,unsafe是什麼?this指的是什麼?valueOffset又是什麼呢?操作系統

咱們接着查看atoInteger源碼:線程

0fNpomWaHdg


咱們發現Unsafe以及valueOffset都是從一個對象中獲取到的。

那麼this指的是什麼?其實this就是當前atoInteger對象。

那麼Unsafe對象在哪裏呢?

0fNpomvwi1I


0fNponFRYJc


0fNponZy1aK


咱們想要看源碼,怎麼查看呢?發現不能看源碼啊。別急,這個文件的源碼能夠從openJdk的源碼中查到。

接着,咱們來查看OpenJdk8的源碼:

(PS:下載OpenJdk8源碼凱哥這裏就不贅述了。在文章最後,凱哥給出)

下載完,解壓以後,文件位置:openjdk\jdk\src\share\classes\sun\misc。以下圖:

0fNponzxFTc


咱們來看看Unsafe類上面的註解:

0fNpooMURF2


A collection of methods for performing low-level, unsafe operations.

什麼意思呢?用於執行底層的(low-level,)、不安全操做的方法的集合。

就是說,這個類能夠直接操做底層數據的。

須要說明的是:在這個對象中大量的方法使用了native來修飾(據網友統計高達82個)

0fNpoofVtBY


咱們知道,Java的方法使用native關鍵字修飾的,說明這個方法不是Java自身的方法(非Java方法),可能調用的是其餘語言的。如C或C++語言的方法。

咱們再來看看:unsafe.objectFieldOffse()中的

0fNpop3RaqW


這個方法就是返回一個內存中訪問偏移量。

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

0fNpopPFYUy


在unsafe類中compareAndSwapInt方法也是native的。咱們在來看看這個方法調用操做系統底層C++的代碼:

0fNpophJhuC


說明:

jint *addr:主內存中的變量值

old:對象工做區域的值

new_val:將要改變的值。

這三個是否是很熟悉,對。就是CAS的三個參數。

0fNpoqLV5km


分析第13行爲何返回false:

在11行的時候,設置主內存的變量值V=1.

在12行後,更新爲V=2020了。

當執行到第13行的時候,

主內存:V=2020

程序工做區變量值jint *addr A的值:A=1

new_val:1024。

從調用C++代碼咱們能夠分析到:

0fNpoqfyETI


在第5行的時候,由於1!=2020,因此return 的result就是false.

因此第13行輸出的是false.

思考3:atoInteger.getAndIncrement()是怎麼保證數據一致性的

0fNpor3Nrmq


調用的是getAndAddInt方法。接着查看unsafe的源碼,就會發現CAS保證原子性的終極代碼。

CAS保證原子性終極方法,以下圖:

0fNporSKX56


看看:getObjectVolatile。方法發現是native.以下圖:

0fNporx96Ya


再來看看compareAndSwapObject:

0fNpoubizSK


發現是native修飾的方法。說明不是Java的方法。這個咱們等會再細說。

先來研究getAndSetObject:

0fNpoutyrgm


源碼:

0fNpovdNjVo


咱們來模擬:atoInteger.getAndIncrement();

假設默認值是0. 主內存的值是0

在調用getAndSetObject方法的幾個參數說明:

Var1:當前atoInteger對象

Var2:當前偏移量(內存地址所在位置。如:三排四列)

Vart4:默認就是1

Var5:獲取到的主內存的值

Var5+var4:將要更新的值。

從源碼,咱們看到是do while語句。爲何不是while語句呢?由於先要獲取到主內存中變量最新的值,而後再判斷。因此選用了do while語句。

咱們來看看當CPU1線程1和CPU2線程B來執行的時候:

0fNpowHYSdU


兩個線程都從主內存copay了i的值到本身工做內存空間後,進行+1的操做。

假設線程1再執行+1操做後,準備往主內存回寫數據的時候,CPU1被掛起。而後CPU2競爭到資源以後,也操做i+1後,將更新後的值回寫到了主內存中。而後切換到CPU1了,CPU1接着執行。對比代碼分析:

0fNpowdHSwS


線程1在執行do後獲得的值var5=1而不是0

而後while裏面執行:var1和var2運算後的結果是0(工做區的值)。

由於0!=5 .因此this.comparAndSwapInt的值是false.

又由於前面有個! 非得符號。也就是!false。咱們知道!false就是true.

也就是while(true)。While(true)後,接着循環執行。線程會放棄原有操做,從新從主內存中獲取到最新數據(此時就是1了),而後再進行操做後。

又到了do,獲取在主內存最新數據是1.接着走while()

由於,var1,var2獲取到工做區的值是1 var5也等於1.1=1,成立了,執行var5+var5=1+1=2,來更新主內存的數據後返回true.

又由於前面有個!非的符號。因此就是while(!true),也就是while(false)。退出循環,返回var5的值。

結論:

經過上面的運行分析,咱們發現atoInteger的getAndIncrement方法保證原子性是unsafe+CAS來保證變量原子性的(其中do while語句就是後面咱們將要學到的自旋)

wx.jpg

相關文章
相關標籤/搜索