JDK源碼學習之 java.util.concurrent.automic包

1、概述  java

  Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環境下無鎖的進行原子操做。原子變量的底層使用了處理器提供的原子指令,可是不一樣的CPU架構可能提供的原子指令不同,也有可能須要某種形式的內部鎖,因此該方法不能絕對保證線程不被阻塞。程序員

  atomic包裏的類基本都是使用Unsafe實現的包裝類,在Atomic包裏一共有12個類,四種原子更新方式,這裏咱們將對着四種方式進一步分析。算法

2、解析
  1. 原子更新基本類型類
  用於經過原子的方式更新基本類型,Atomic包提供瞭如下三個類:
    AtomicBoolean:原子更新布爾類型。
    AtomicInteger:原子更新整型。
    AtomicLong:原子更新長整型。數組

  這裏咱們着重介紹下AtomicInteger這個類的經常使用方法,以下:
  (1)int addAndGet(int delta) :安全

    以原子方式將輸入的數值與實例中的值(AtomicInteger裏的value)相加,並返回結果
  (2)boolean compareAndSet(int expect, int update) :多線程

    若是輸入的數值等於預期值,則以原子方式將該值設置爲輸入的值。
  (3)int getAndIncrement():架構

    以原子方式將當前值加1,注意:這裏返回的是自增前的值。
  (4)void lazySet(int newValue)
  (5)int getAndSet(int newValue):性能

    以原子方式設置爲newValue的值,並返回舊值。this

  參考實例以下:atom

      AtomicInteger a = new AtomicInteger(1);
      a.addAndGet(2); 
      System.out.println(a); //3
      a.getAndIncrement();
      System.out.println(a.get()); //4

 這裏咱們延伸一下,介紹下CAS的實現原理:

  CAS(Compare and Swap,比較並操做),  CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。

  CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然什麼都不作。

        AtomicInteger中的 incrementAndGet 方法源碼以下:

  public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
  }

   經過源碼能夠看到,方法執行步驟爲:先獲取到當前的 value 屬性值,而後將 value 加 1,賦值給一個局部的 next 變量,然而,這兩步都是非線程安全的,可是內部有一個死循環,不斷去作compareAndSet操做,直到成功爲止,也就是修改的根本在compareAndSet方法裏面,compareAndSet()方法的代碼以下:

    public final boolean compareAndSet(int expect, int update) {
	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

  compareAndSwapInt 方法是一個native 方法,以下:

  public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  
  因此compareAndSet傳入的值爲執行方法時獲取到的value屬性值,next爲加 1 後的值,compareAndSet調用native的compareAndSwapInt方法來完成,
compareAndSwapInt 基於的是CPU的CAS指令來實現的。因此基於CAS的操做可認爲是無阻塞的,一個線程的失敗或掛起不會引發其它線程也失敗或掛起。而且因爲CAS操做是CPU原語,因此性能比較好。
  可是須要注意的是:在CAS操做中,會出現ABA的問題,即若是V值先由A變爲B,再由B變爲A,那麼會仍然認爲是發生了變化,並須要從新執行算法中的步驟。
  對應的解決方案是:不僅更新某個引用的值,而是更新兩個值,包括一個引用和一個版本號,即便這個值由A變爲B,而後又變爲A,版本號也是不一樣的。
  例如:AtomicMarkableReference和AtomicStampedReference支持在兩個變量上執行原子的條件更新,
AtomicStampedReference更新一個「對象-引用」二元組,經過在引用上加上「版本號」,從而避免ABA問題,AtomicMarkableReference將更新一個「對象引用-布爾值」的二元組。

 2. 原子更新數組類

  經過原子的方式更新數組裏的某個元素,Atomic包提供瞭如下三個類:
    AtomicIntegerArray:原子更新整型數組裏的元素,主要用於原子的方式更新數組裏的整型。
    AtomicLongArray:原子更新長整型數組裏的元素。
    AtomicReferenceArray:原子更新引用類型數組裏的元素。

  這裏咱們一樣着重介紹下AtomicIntegerArray類的經常使用方法,以下:

  (1)int addAndGet(int i, int delta):

    以原子方式將輸入值與數組中索引i的元素相加。
  (2)boolean compareAndSet(int i, int expect, int update):

    若是當前值等於預期值,則以原子方式將數組位置i的元素設置成update值。

  參考實例以下:

  int[] arr = new int[]{1,2,3};
  AtomicIntegerArray arr1= new AtomicIntegerArray(arr);
  arr1.getAndAdd(2,4);
  arr1.getAndSet(0,2);
  System.out.println(arr[2]); //3
  System.out.println(arr1.get(2)); //7

  注:AtomicIntegerArray對內部的數組元素進行修改時,不會影響到傳入的數組,緣由是數組值經過構造方法傳遞進去,而後AtomicIntegerArray會將當前數組複製一份。

 3. 原子更新引用類型

  原子更新基本類型的AtomicInteger,只能更新一個變量,若是要原子的更新多個變量,就須要使用這個原子更新引用類型提供的類。Atomic包提供瞭如下三個類:
    AtomicReference:原子更新引用類型。
    AtomicReferenceFieldUpdater:原子更新引用類型裏的字段。
    AtomicMarkableReference:原子更新帶有標記位的引用類型。能夠原子的更新一個布爾類型的標記位和引用類型。構造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

  參考實例以下:

  static class User{
        String id;
        String name;
        volatile int age;

        public User(String id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }

        public String getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
  }
  public static void main(String[] args) {
        User user1 = new User("1", "LiLei", 11);
        User user2 = new User("2", "HanMeimei", 12);

        AtomicReference<User> user = new AtomicReference<User>(user1);
        System.out.println(user1.getName());  //LiLei
        user.getAndSet(user2);
        System.out.println(user.get().getName()); //HanMeimei 
  }   

 4. 原子更新字段類

  若是咱們只須要某個類裏的某個字段,那麼就須要使用原子更新字段類,Atomic包提供瞭如下三個類:
    AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
    AtomicLongFieldUpdater:原子更新長整型字段的更新器。
    AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用於原子的更數據和數據的版本號,能夠解決使用CAS進行原子更新時,可能出現的ABA問題。
  原子更新字段類都是抽象類,每次使用都時候必須使用靜態方法newUpdater建立一個更新器。原子更新類的字段的必須使用public volatile修飾符。

  AtomicIntegerFieldUpdater的例子代碼以下:

  AtomicIntegerFieldUpdater<User> userUpdate = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
  userUpdate.getAndIncrement(user2);
  System.out.println(user2.getAge()); //13

  

  參考博文:

  http://ifeve.com/java-atomic/  http://blog.csdn.net/qfycc92/article/details/46489553

相關文章
相關標籤/搜索