溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java原子操作CAS原理解析

發(fā)布時間:2020-08-22 09:06:33 來源:腳本之家 閱讀:163 作者:ねぇ 欄目:編程語言

一、CAS(Compare And Set)

Compare And Set(或Compare And Swap),CAS是解決多線程并行情況下使用鎖造成性能損耗的一種機制,CAS操作包含三個操作數(shù)——內存位置(V)、預期原值(A)、新值(B)。如果內存位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會在CAS指令之前返回該位置的值。CAS有效地說明了“我認為位置V應該包含值A;如果包含該值,則將B放到這個位置;否則,不要更改該位置,只告訴我這個位置現(xiàn)在的值即可。

​ 在java中可以通過鎖和循環(huán)CAS的方式來實現(xiàn)原子操作。Java中 java.util.concurrent.atomic包相關類就是 CAS的實現(xiàn),atomic包里包括以下類:

AtomicBoolean 可以用原子方式更新的 boolean 值。
AtomicInteger 可以用原子方式更新的 int 值。
AtomicIntegerArray 可以用原子方式更新其元素的 int 數(shù)組。
AtomicIntegerFieldUpdater 基于反射的實用工具,可以對指定類的指定 volatile int 字段進行原子更新。
AtomicLong 可以用原子方式更新的 long 值。
AtomicLongArray 可以用原子方式更新其元素的 long 數(shù)組。
AtomicLongFieldUpdater 基于反射的實用工具,可以對指定類的指定 volatile long 字段進行原子更新。
AtomicMarkableReference AtomicMarkableReference 維護帶有標記位的對象引用,可以原子方式對其進行更新。
AtomicReference 可以用原子方式更新的對象引用。
AtomicReferenceArray 可以用原子方式更新其元素的對象引用數(shù)組。
AtomicReferenceFieldUpdater<T,V> 基于反射的實用工具,可以對指定類的指定 volatile 字段進行原子更新。
AtomicStampedReference AtomicStampedReference 維護帶有整數(shù)“標志”的對象引用,可以用原子方式對其進行更新。

二、AtomicInteger

AtomicInteger可以用原子方式更新的 int 值。AtomicInteger 可用在應用程序中(如以原子方式增加的計數(shù)器),并且不能用于替換 Integer。但是,此類確實擴展了 Number,允許那些處理基于數(shù)字類的工具和實用工具進行統(tǒng)一訪問。 我們拿 AtomicInteger為例來學習下 CAS操作是如何實現(xiàn)的。

通常情況下,在 Java中,i++等類似操作并不是線程安全的,因為 i++可分為三個獨立的操作:獲取變量當前值,為該值+1,然后寫回新的值。在沒有額外資源可以利用的情況下,只能使用加鎖才能保證讀-改-寫這三個操作時“原子性”的。但是利用加鎖的方式來實現(xiàn)該功能的話,代碼將非常復雜及難以維護,如:

synchronized (lock) { 
  i++; 
} 

相關類中還需要增加 Object lock等額外標志,這樣就帶來了很多麻煩,增加了很多業(yè)務無關代碼,給開發(fā)與維護帶來了不便。
​ 然而利用 atomic包中相關類型就可以很簡單實現(xiàn)此操作,以下是一個計數(shù)程序實例:

public class Counter {
  private AtomicInteger ai = new AtomicInteger();
  private int i = 0;
 
  public static void main(String[] args) { 
    final Counter cas = new Counter(); 
    List<Thread> threads = new ArrayList<Thread>();
    // 添加100個線程 
    for (int j = 0; j < 100; j++) { 
      threads.add(new Thread(new Runnable() {
        public void run() { 
          // 執(zhí)行100次計算,預期結果應該是10000 
          for (int i = 0; i < 100; i++) { 
            cas.count(); 
            cas.safeCount(); 
          } 
        } 
      })); 
    } 
    //開始執(zhí)行 
    for (Thread t : threads) {
      t.start(); 
    } 
    // 等待所有線程執(zhí)行完成 
    for (Thread t : threads) {
      try { 
        t.join(); 
      } catch (InterruptedException e) { 
        e.printStackTrace(); 
      } 
    } 
    System.out.println("非線程安全計數(shù)結果:"+cas.i); 
    System.out.println("線程安全計數(shù)結果:"+cas.ai.get()); 
  } 
 
  /** 使用CAS實現(xiàn)線程安全計數(shù)器 */ 
  private void safeCount() { 
    for (;;) { 
      int i = ai.get(); 
      // 如果當前值 == 預期值,則以原子方式將該值設置為給定的更新值 
      boolean suc = ai.compareAndSet(i, ++i); 
      if (suc) { 
        break; 
      } 
    } 
  } 
  /** 非線程安全計數(shù)器 */ 
  private void count() { 
    i++; 
  } 
} 
/**
非線程安全計數(shù)結果:9942
線程安全計數(shù)結果:10000
*/

其中非線程安全計數(shù)器所計算的結果每次都不相同且不正確,而線程安全計數(shù)器計算的結果每次都是正確的。

三、存在的問題

CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題:ABA問題、循環(huán)時間長開銷大、只能保證一個共享變量的原子操作。

ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。 從Java1.5開始JDK的 atomic包里提供了一個類AtomicStampedReference 來解決ABA問題。這個類的 compareAndSet方法作用是首先檢查當前引用是否等于預期引用,并且當前標志是否等于預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。

循環(huán)時間長開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源,延遲的時間取決于具體實現(xiàn)的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環(huán)的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率?!?/p>

只能保證一個共享變量的原子操作:當對一個共享變量執(zhí)行操作時,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI