Java線程(十)----CAS

前言

       在Java併發包中有這樣一個包,java.util.concurrent.atomic,該包是對Java部分數據類型的原子封裝,在原有數據類型的基礎上,提供了原子性的操作方法,保證了線程安全。下面以AtomicInteger爲例,來看一下是如何實現的。

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public final int incrementAndGet() {  
  2.     for (;;) {  
  3.         int current = get();  
  4.         int next = current + 1;  
  5.         if (compareAndSet(current, next))  
  6.             return next;  
  7.     }  
  8. }  
[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public final int decrementAndGet() {  
  2.     for (;;) {  
  3.         int current = get();  
  4.         int next = current - 1;  
  5.         if (compareAndSet(current, next))  
  6.             return next;  
  7.     }  
  8. }  

       以這兩個方法爲例,incrementAndGet方法相當於原子性的++i,decrementAndGet方法相當於原子性的--i(根據第一章第二章我們知道++i或--i不是一個原子性的操作),這兩個方法中都沒有使用阻塞式的方式來保證原子性(如Synchronized),那它們是如何保證原子性的呢,下面引出CAS。

Compare And Swap

       CAS 指的是現代 CPU 廣泛支持的一種對內存中的共享數據進行操作的一種特殊指令。這個指令會對內存中的共享數據做原子的讀寫操作。簡單介紹一下這個指令的操作過程:首先,CPU 會將內存中將要被更改的數據與期望的值做比較。然後,當這兩個值相等時,CPU 纔會將內存中的數值替換爲新的值。否則便不做操作。最後,CPU 會將舊的數值返回。這一系列的操作是原子的。它們雖然看似複雜,但卻是 Java 5 併發機制優於原有鎖機制的根本。簡單來說,CAS 的含義是“我認爲原有的值應該是什麼,如果是,則將原有的值更新爲新值,否則不做修改,並告訴我原來的值是多少”。(這段描述引自《Java併發編程實踐》)
       簡單的來說,CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則返回V。這是一種樂觀鎖的思路,它相信在它修改之前,沒有其它線程去修改它;而Synchronized是一種悲觀鎖,它認爲在它修改之前,一定會有其它線程去修改它,悲觀鎖效率很低。下面來看一下AtomicInteger是如何利用CAS實現原子性操作的。

volatile變量

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private volatile int value;  
       首先聲明瞭一個volatile變量value,在第二章我們知道volatile保證了變量的內存可見性,也就是所有工作線程中同一時刻都可以得到一致的值。
[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public final int get() {  
  2.     return value;  
  3. }  

Compare And Set

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. // setup to use Unsafe.compareAndSwapInt for updates  
  2. private static final Unsafe unsafe = Unsafe.getUnsafe();  
  3. private static final long valueOffset;// 注意是靜態的  
  4.   
  5. static {  
  6.   try {  
  7.     valueOffset = unsafe.objectFieldOffset  
  8.         (AtomicInteger.class.getDeclaredField("value"));// 反射出value屬性,獲取其在內存中的位置  
  9.   } catch (Exception ex) { throw new Error(ex); }  
  10. }  
  11.   
  12. public final boolean compareAndSet(int expect, int update) {  
  13.   return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
  14. }  
       比較並設置,這裏利用Unsafe類的JNI方法實現,使用CAS指令,可以保證讀-改-寫是一個原子操作。compareAndSwapInt有4個參數,this - 當前AtomicInteger對象,Offset - value屬性在內存中的位置(需要強調的是不是value值在內存中的位置),expect - 預期值,update - 新值,根據上面的CAS操作過程,當內存中的value值等於expect值時,則將內存中的value值更新爲update值,並返回true,否則返回false。在這裏我們有必要對Unsafe有一個簡單點的認識,從名字上來看,不安全,確實,這個類是用於執行低級別的、不安全操作的方法集合,這個類中的方法大部分是對內存的直接操作,所以不安全,但當我們使用反射、併發包時,都間接的用到了Unsafe。

循環設置

       現在在來看開篇提到的兩個方法,我們拿incrementAndGet來分析一下其實現過程。
[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public final int incrementAndGet() {  
  2.     for (;;) {// 這樣優於while(true)  
  3.         int current = get();// 獲取當前值  
  4.         int next = current + 1;// 設置更新值  
  5.         if (compareAndSet(current, next))  
  6.             return next;  
  7.     }  
  8. }  
       循環內,獲取當前值並設置更新值,調用compareAndSet進行CAS操作,如果成功就返回更新至,否則重試到成功爲止。這裏可能存在一個隱患,那就是循環時間過長,總是在當前線程compareAndSet時,有另一個線程設置了value(點子太背了),這個當然是屬於小概率時間,目前Java貌似還不能處理這種情況。

缺點

       雖然使用CAS可以實現非阻塞式的原子性操作,但是會產生ABA問題,關於ABA問題,計劃單拿出一章來整理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章