Java併發程式設計之CAS二原始碼追根溯源
Java併發程式設計之CAS二原始碼追根溯源
在上一篇文章中,我們知道了什麼是CAS以及CAS的執行流程,在本篇文章中,我們將跟著原始碼一步一步的檢視CAS最底層實現原理。
本篇是《凱哥(凱哥Java:kagejava)併發程式設計學習》系列之《CAS系列》教程的第二篇:從原始碼追根溯源檢視CAS最底層是怎麼實現的。
本文主要內容:CAS追根溯源,徹底找到CAS的根在哪裡。
一:檢視AtomicInteger.compareAndSet原始碼
通過上一篇文章學習,我們知道了AtomicInteger.compareAndSet方法不加鎖可以保證原子性(其原理就是unsafe+cas實現的),我們來看看其原始碼:
思考1:變數可見性
AtomicInteger物件(下文凱哥簡稱:atoInteger)怎麼保證變數記憶體可見性呢?
檢視原始碼:
思考2:為什麼上一篇13行的i.compareAndSet(1,1024)是false
我們來看看atoInteger的compareAndSet方法。凱哥在上面添加了註釋。
在呼叫unsafe的compareAndSwapInt這個方法的時候,unsafe是什麼?this指的是什麼?valueOffset又是什麼呢?
我們接著檢視atoInteger原始碼:
我們發現Unsafe以及valueOffset都是從一個物件中獲取到的。
那麼this指的是什麼?其實this就是當前atoInteger物件。
那麼Unsafe物件在哪裡呢?
我們想要看原始碼,怎麼檢視呢?發現不能看原始碼啊。別急,這個檔案的原始碼可以從openJdk的原始碼中查到。
接著,我們來檢視OpenJdk8的原始碼:
(PS:下載OpenJdk8原始碼凱哥這裡就不贅述了。在文章最後,凱哥給出)
下載完,解壓之後,檔案位置:openjdk\jdk\src\share\classes\sun\misc。如下圖:
我們來看看Unsafe類上面的註解:
A collection of methods for performing low-level, unsafe operations.
什麼意思呢?用於執行底層的(low-level,)、不安全操作的方法的集合。
就是說,這個類可以直接操作底層資料的。
需要說明的是:在這個物件中大量的方法使用了native來修飾(據網友統計高達82個)
我們知道,Java的方法使用native關鍵字修飾的,說明這個方法不是Java自身的方法(非Java方法),可能呼叫的是其他語言的。如C或C++語言的方法。
我們再來看看:unsafe.objectFieldOffse()中的
這個方法就是返回一個記憶體中訪問偏移量。
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
在unsafe類中compareAndSwapInt方法也是native的。我們在來看看這個方法呼叫作業系統底層C++的程式碼:
說明:
jint *addr:主記憶體中的變數值
old:物件工作區域的值
new_val:將要改變的值。
這三個是不是很熟悉,對。就是CAS的三個引數。
分析第13行為什麼返回false:
在11行的時候,設定主記憶體的變數值V=1.
在12行後,更新為V=2020了。
當執行到第13行的時候,
主記憶體:V=2020
程式工作區變數值jint *addr A的值:A=1
new_val:1024。
從呼叫C++程式碼我們可以分析到:
在第5行的時候,因為1!=2020,所以return 的result就是false.
所以第13行輸出的是false.
思考3:atoInteger.getAndIncrement()是怎麼保證資料一致性的
呼叫的是getAndAddInt方法。接著檢視unsafe的原始碼,就會發現CAS保證原子性的終極程式碼。
CAS保證原子性終極方法,如下圖:
看看:getObjectVolatile。方法發現是native.如下圖:
再來看看compareAndSwapObject:
發現是native修飾的方法。說明不是Java的方法。這個我們等會再細說。
先來研究getAndSetObject:
原始碼:
我們來模擬:atoInteger.getAndIncrement();
假設預設值是0. 主記憶體的值是0
在呼叫getAndSetObject方法的幾個引數說明:
Var1:當前atoInteger物件
Var2:當前偏移量(記憶體地址所在位置。如:三排四列)
Vart4:預設就是1
Var5:獲取到的主記憶體的值
Var5+var4:將要更新的值。
從原始碼,我們看到是do while語句。為什麼不是while語句呢?因為先要獲取到主記憶體中變數最新的值,然後再判斷。所以選用了do while語句。
我們來看看當CPU1執行緒1和CPU2執行緒B來執行的時候:
兩個執行緒都從主記憶體copay了i的值到自己工作記憶體空間後,進行+1的操作。
假設執行緒1再執行+1操作後,準備往主記憶體回寫資料的時候,CPU1被掛起。然後CPU2競爭到資源之後,也操作i+1後,將更新後的值回寫到了主記憶體中。然後切換到CPU1了,CPU1接著執行。對比程式碼分析:
執行緒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語句就是後面我們將要學到的自旋)
相關推薦
Java併發程式設計之CAS二原始碼追根溯源
Java併發程式設計之CAS二原始碼追根溯源 在上一篇文章中,我們知道了什麼是CAS以及CAS的執行流程,在本篇文章中,我們將跟著原始碼一步一步的檢視CAS最底層實現原理。 本篇是《凱哥(凱哥Java:kagejava)併發程式設計學習》系列之《CAS系列》教程的第二篇:從原始碼追根溯源檢視CAS最底層是怎麼
Java併發程式設計之Semaphore(二)
一.介紹 Semaphore是一種在多執行緒環境下使用的設施,該設施負責協調各個執行緒,以保證它們能夠正確、合理的使用公共資源的設施,也是作業系統中用於控制程序同步互斥的量。Semaphore是一種計數訊號量,用於管理一組資源,內部是基於AQS的共享模式。它相當於給執行緒規定一個量從而控制允
Java併發程式設計之CAS
CAS(Compare and swap)比較和替換是設計併發演算法時用到的一種技術。簡單來說,比較和替換是使用一個期望值和一個變數的當前值進行比較,如果當前變數的值與我們期望的值相等,就使用一個新值替換當前變數的值。這聽起來可能有一點複雜但是實際上你理解之後發現很簡單,接下來,讓我們跟深入
Java併發程式設計之CAS演算法
在多執行緒環境下,我們要實現對一個變數自增的話,往往會使用java.util.concurrent.atomic包下的相關實現類。 如下: public class TestAtomic { public static void main(String
Java併發程式設計之CAS第一篇-什麼是CAS
Java併發程式設計之CAS第一篇-什麼是CAS 通過前面幾篇的學習,我們對併發程式設計兩個高頻知識點了解了其中的一個—volatitl。從這一篇文章開始,我們將要學習另一個知識點—CAS.本篇是《凱哥併發程式設計學習》系列之《CAS系列》教程的第一篇:什麼是CAS。 本文主要內容: 生活中舉例;CAS定義;
Java併發程式設計之CAS第三篇-CAS的缺點及解決辦法
Java併發程式設計之CAS第三篇-CAS的缺點 通過前兩篇的文章介紹,我們知道了CAS是什麼以及檢視原始碼瞭解CAS原理。那麼在多執行緒併發環境中,的缺點是什麼呢?這篇文章我們就來討論討論 本篇是《凱哥(凱哥Java:kagejava)併發程式設計學習》系列之《CAS系列》教程的第三篇:CAS的缺點有哪些?
java併發程式設計之利用CAS保證操作的原子性
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger at
java併發程式設計之執行緒,執行緒池,ansync執行緒池原始碼解析
前言 java開源長了, 程式碼久了,網上對於執行緒那是眾說紛紜,一直縈繞我心頭的,jdk執行緒池好還是spring執行緒池好
Java併發程式設計之CyclicBarrier
CyclicBarrier可以控制這樣的場景: 對多個執行緒,他們執行自己程式碼(執行run方法)的時間不一樣; 比如有3個執行緒,其run方法執行時間分別為1s, 2s, 3s。如果我們想在三個執行緒都完成自己的任務時執行相應的操作,CyclicBarrier就派上用場了。 寫了一
Java併發程式設計之鎖機制之LockSupport工具
關於文章涉及到的jdk原始碼,這裡把最新的jdk原始碼分享給大家----->jdk原始碼 前言 在上篇文章《Java併發程式設計之鎖機制之AQS(AbstractQueuedSynchronizer)》中我們瞭解了整個AQS的內部結構,與其獨佔式與共享式獲取同步狀態的實現
Java併發程式設計之執行緒生命週期、守護執行緒、優先順序和join、sleep、yield
Java併發程式設計中,其中一個難點是對執行緒生命週期的理解,和多種執行緒控制方法、執行緒溝通方法的靈活運用。這些方法和概念之間彼此聯絡緊密,共同構成了Java併發程式設計基石之一。 Java執行緒的生命週期 Java執行緒類定義了New、Runnable、Running Man、Blocked和Dead
Java併發程式設計之執行緒安全、執行緒通訊
Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程
Java併發程式設計之ThreadGroup
ThreadGroup是Java提供的一種對執行緒進行分組管理的手段,可以對所有執行緒以組為單位進行操作,如設定優先順序、守護執行緒等。 執行緒組也有父子的概念,如下圖: 執行緒組的建立 1 public class ThreadGroupCreator { 2 3 publi
Java併發程式設計之Exchanger
概述 用於執行緒間資料的交換。它提供一個同步點,在這個同步點,兩個執行緒可以交換彼此的資料。這兩個執行緒通過exchange方法交換資料,如果第一個執行緒先執行exchange()方法,它會一直等待第二個執行緒也執行exchange方法,當兩個執行緒都到達同步點時,這兩個執行緒就可以交換資料
java併發程式設計之使用 CountDownLatch 控制多個執行緒執行順序
有時候會有這樣的需求,多個執行緒同時工作,然後其中幾個可以隨意併發執行,但有一個執行緒需要等其他執行緒工作結束後,才能開始。舉個例子,開啟多個執行緒分塊下載一個大檔案,每個執行緒只下載固定的一截,最後由另外一個執行緒來拼接所有的分段,那麼這時候我們可以考慮使用CountDownLatch來控制併發。
JAVA併發程式設計之基本概念
1、鎖是對物件訪問的時候,通過對物件加鎖,防止並行訪問的控制手段;對物件加鎖成功,代表我持有這個物件的監視器,解鎖,代表釋放了這個物件的監視器。 拿到物件的監視器,肯定是對物件加鎖成功的;對物件加鎖成功 ,程式可以主動Watiing或者Time_waiting在物件監視器上。 2、鎖與監
java併發程式設計之happens-before原則,先行發生原則
下文為自己學習筆記。 關鍵詞理解: JMM:java memory model java記憶體模型 int a=1;//A int b=3;//B int c=a*b;//C 在上邊這段程式碼中,有A\B\C個語句 C依賴於A、B兩個語句,所以Ahappens-before於C,
Java併發程式設計之鎖機制之Condition介面
前言 在前面的文章中,我曾提到過,整個Lock介面下實現的鎖機制中AQS(AbstractQueuedSynchronizer,下文都稱之為AQS)與Condition才是真正的實現者。也就說Condition在整個同步元件的基礎框架中也起著非常重要的作用,既然它如此重要與犀利,那麼現在我
Java併發程式設計之Locks鎖
Java併發程式設計:Lock 此文轉載自: http://www.cnblogs.com/dolphin0520/p/3923167.html 從Java 5之後,在java.util.concurrent.locks包下提供了另外一種方式來實現同
java併發程式設計之顯示鎖
顯示鎖 為了保證共享物件的安全性,常用的機制有: volatile 關鍵字 synchronized ReentrantLock 顯示鎖 1.1 ReentrantLock ReentrantLock實現了Lock介面。Lock介面定義一組抽象的