JUC 中的 Atomic 原子類總結

 

1 Atomic 原子類介紹

Atomic 翻譯成中文是原子的意思。在化學上,咱們知道原子是構成通常物質的最小單位,在化學反應中是不可分割的。在咱們這裏 Atomic 是指一個操做是不可中斷的。即便是在多個線程一塊兒執行的時候,一個操做一旦開始,就不會被其餘線程干擾。java

因此,所謂原子類說簡單點就是具備原子/原子操做特徵的類。git

併發包 java.util.concurrent 的原子類都存放在java.util.concurrent.atomic下,以下圖所示。數組

JUC原子類概覽

根據操做的數據類型,能夠將JUC包中的原子類分爲4類安全

基本類型多線程

使用原子的方式更新基本類型併發

  • AtomicInteger:整型原子類
  • AtomicLong:長整型原子類
  • AtomicBoolean :布爾型原子類

數組類型app

使用原子的方式更新數組裏的某個元素jvm

  • AtomicIntegerArray:整型數組原子類
  • AtomicLongArray:長整型數組原子類
  • AtomicReferenceArray :引用類型數組原子類

引用類型ide

  • AtomicReference:引用類型原子類
  • AtomicReferenceFieldUpdater:原子更新引用類型裏的字段
  • AtomicMarkableReference :原子更新帶有標記位的引用類型

對象的屬性修改類型ui

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器
  • AtomicStampedReference :原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用於解決原子的更新數據和數據的版本號,能夠解決使用 CAS 進行原子更新時可能出現的 ABA 問題。
  • AtomicMarkableReference:原子更新帶有標記的引用類型。該類將 boolean 標記與引用關聯起來,也能夠解決使用 CAS 進行原子更新時可能出現的 ABA 問題。

CAS ABA 問題

  • 描述: 第一個線程取到了變量 x 的值 A,而後巴拉巴拉幹別的事,總之就是隻拿到了變量 x 的值 A。這段時間內第二個線程也取到了變量 x 的值 A,而後把變量 x 的值改成 B,而後巴拉巴拉幹別的事,最後又把變量 x 的值變爲 A (至關於還原了)。在這以後第一個線程終於進行了變量 x 的操做,可是此時變量 x 的值仍是 A,因此 compareAndSet 操做是成功。
  • 例子描述(可能不太合適,但好理解): 年初,現金爲零,而後經過正常勞動賺了三百萬,以後正常消費了(好比買房子)三百萬。年底,雖然現金零收入(可能變成其餘形式了),可是賺了錢是事實,仍是得交稅的!
  • 代碼例子(以AtomicInteger爲例)
import java.util.concurrent.atomic.AtomicInteger;  public class AtomicIntegerDefectDemo {  public static void main(String[] args) {  defectOfABA();  }   static void defectOfABA() {  final AtomicInteger atomicInteger = new AtomicInteger(1);   Thread coreThread = new Thread(  () -> {  final int currentValue = atomicInteger.get();  System.out.println(Thread.currentThread().getName() + " ------ currentValue=" + currentValue);   // 這段目的:模擬處理其餘業務花費的時間  try {  Thread.sleep(300);  } catch (InterruptedException e) {  e.printStackTrace();  }   boolean casResult = atomicInteger.compareAndSet(1, 2);  System.out.println(Thread.currentThread().getName()  + " ------ currentValue=" + currentValue  + ", finalValue=" + atomicInteger.get()  + ", compareAndSet Result=" + casResult);  }  );  coreThread.start();   // 這段目的:爲了讓 coreThread 線程先跑起來  try {  Thread.sleep(100);  } catch (InterruptedException e) {  e.printStackTrace();  }   Thread amateurThread = new Thread(  () -> {  int currentValue = atomicInteger.get();  boolean casResult = atomicInteger.compareAndSet(1, 2);  System.out.println(Thread.currentThread().getName()  + " ------ currentValue=" + currentValue  + ", finalValue=" + atomicInteger.get()  + ", compareAndSet Result=" + casResult);   currentValue = atomicInteger.get();  casResult = atomicInteger.compareAndSet(2, 1);  System.out.println(Thread.currentThread().getName()  + " ------ currentValue=" + currentValue  + ", finalValue=" + atomicInteger.get()  + ", compareAndSet Result=" + casResult);  }  );  amateurThread.start();  } }

輸出內容以下:

Thread-0 ------ currentValue=1
Thread-1 ------ currentValue=1, finalValue=2, compareAndSet Result=true Thread-1 ------ currentValue=2, finalValue=1, compareAndSet Result=true Thread-0 ------ currentValue=1, finalValue=2, compareAndSet Result=true

下面咱們來詳細介紹一下這些原子類。

2 基本類型原子類

2.1 基本類型原子類介紹

使用原子的方式更新基本類型

  • AtomicInteger:整型原子類
  • AtomicLong:長整型原子類
  • AtomicBoolean :布爾型原子類

上面三個類提供的方法幾乎相同,因此咱們這裏以 AtomicInteger 爲例子來介紹。

AtomicInteger 類經常使用方法

public final int get() //獲取當前的值 public final int getAndSet(int newValue)//獲取當前的值,並設置新的值 public final int getAndIncrement()//獲取當前的值,並自增 public final int getAndDecrement() //獲取當前的值,並自減 public final int getAndAdd(int delta) //獲取當前的值,並加上預期的值 boolean compareAndSet(int expect, int update) //若是輸入的數值等於預期值,則以原子方式將該值設置爲輸入值(update) public final void lazySet(int newValue)//最終設置爲newValue,使用 lazySet 設置以後可能致使其餘線程在以後的一小段時間內仍是能夠讀到舊的值。

2.2 AtomicInteger 常見方法使用

import java.util.concurrent.atomic.AtomicInteger;  public class AtomicIntegerTest {   public static void main(String[] args) {  // TODO Auto-generated method stub  int temvalue = 0;  AtomicInteger i = new AtomicInteger(0);  temvalue = i.getAndSet(3);  System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3  temvalue = i.getAndIncrement();  System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4  temvalue = i.getAndAdd(5);  System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9  }  }

2.3 基本數據類型原子類的優點

經過一個簡單例子帶你們看一下基本數據類型原子類的優點

①多線程環境不使用原子類保證線程安全(基本數據類型)

class Test {  private volatile int count = 0;  //若要線程安全執行執行count++,須要加鎖  public synchronized void increment() {  count++;  }   public int getCount() {  return count;  } }

②多線程環境使用原子類保證線程安全(基本數據類型)

class Test2 {  private AtomicInteger count = new AtomicInteger();   public void increment() {  count.incrementAndGet();  }  //使用AtomicInteger以後,不須要加鎖,也能夠實現線程安全。  public int getCount() {  return count.get();  } } 

2.4 AtomicInteger 線程安全原理簡單分析

AtomicInteger 類的部分源碼:

    // setup to use Unsafe.compareAndSwapInt for updates(更新操做時提供「比較並替換」的做用)  private static final Unsafe unsafe = Unsafe.getUnsafe();  private static final long valueOffset;   static {  try {  valueOffset = unsafe.objectFieldOffset  (AtomicInteger.class.getDeclaredField("value"));  } catch (Exception ex) { throw new Error(ex); }  }   private volatile int value;

AtomicInteger 類主要利用 CAS (compare and swap) + volatile 和 native 方法來保證原子操做,從而避免 synchronized 的高開銷,執行效率大爲提高。

CAS的原理是拿指望的值和本來的一個值做比較,若是相同則更新成新的值。UnSafe 類的 objectFieldOffset() 方法是一個本地方法,這個方法是用來拿到「原來的值」的內存地址。另外 value 是一個volatile變量,在內存中可見,所以 JVM 能夠保證任什麼時候刻任何線程總能拿到該變量的最新值。

3 數組類型原子類

3.1 數組類型原子類介紹

使用原子的方式更新數組裏的某個元素

  • AtomicIntegerArray:整形數組原子類
  • AtomicLongArray:長整形數組原子類
  • AtomicReferenceArray :引用類型數組原子類

上面三個類提供的方法幾乎相同,因此咱們這裏以 AtomicIntegerArray 爲例子來介紹。

AtomicIntegerArray 類經常使用方法

public final int get(int i) //獲取 index=i 位置元素的值 public final int getAndSet(int i, int newValue)//返回 index=i 位置的當前的值,並將其設置爲新值:newValue public final int getAndIncrement(int i)//獲取 index=i 位置元素的值,並讓該位置的元素自增 public final int getAndDecrement(int i) //獲取 index=i 位置元素的值,並讓該位置的元素自減 public final int getAndAdd(int delta) //獲取 index=i 位置元素的值,並加上預期的值 boolean compareAndSet(int expect, int update) //若是輸入的數值等於預期值,則以原子方式將 index=i 位置的元素值設置爲輸入值(update) public final void lazySet(int i, int newValue)//最終 將index=i 位置的元素設置爲newValue,使用 lazySet 設置以後可能致使其餘線程在以後的一小段時間內仍是能夠讀到舊的值。

3.2 AtomicIntegerArray 常見方法使用


import java.util.concurrent.atomic.AtomicIntegerArray;  public class AtomicIntegerArrayTest {   public static void main(String[] args) {  // TODO Auto-generated method stub  int temvalue = 0;  int[] nums = { 1, 2, 3, 4, 5, 6 };  AtomicIntegerArray i = new AtomicIntegerArray(nums);  for (int j = 0; j < nums.length; j++) {  System.out.println(i.get(j));  }  temvalue = i.getAndSet(0, 2);  System.out.println("temvalue:" + temvalue + "; i:" + i);  temvalue = i.getAndIncrement(0);  System.out.println("temvalue:" + temvalue + "; i:" + i);  temvalue = i.getAndAdd(0, 5);  System.out.println("temvalue:" + temvalue + "; i:" + i);  }  }

4 引用類型原子類

4.1 引用類型原子類介紹

基本類型原子類只能更新一個變量,若是須要原子更新多個變量,須要使用 引用類型原子類。

  • AtomicReference:引用類型原子類
  • AtomicStampedReference:原子更新引用類型裏的字段原子類
  • AtomicMarkableReference :原子更新帶有標記位的引用類型

上面三個類提供的方法幾乎相同,因此咱們這裏以 AtomicReference 爲例子來介紹。

4.2 AtomicReference 類使用示例

import java.util.concurrent.atomic.AtomicReference;  public class AtomicReferenceTest {   public static void main(String[] args) {  AtomicReference<Person> ar = new AtomicReference<Person>();  Person person = new Person("SnailClimb", 22);  ar.set(person);  Person updatePerson = new Person("Daisy", 20);  ar.compareAndSet(person, updatePerson);   System.out.println(ar.get().getName());  System.out.println(ar.get().getAge());  } }  class Person {  private String name;  private int age;   public Person(String name, int age) {  super();  this.name = name;  this.age = age;  }   public String getName() {  return name;  }   public void setName(String name) {  this.name = name;  }   public int getAge() {  return age;  }   public void setAge(int age) {  this.age = age;  }  }

上述代碼首先建立了一個 Person 對象,而後把 Person 對象設置進 AtomicReference 對象中,而後調用 compareAndSet 方法,該方法就是經過 CAS 操做設置 ar。若是 ar 的值爲 person 的話,則將其設置爲 updatePerson。實現原理與 AtomicInteger 類中的 compareAndSet 方法相同。運行上面的代碼後的輸出結果以下:

Daisy
20

4.3 AtomicStampedReference 類使用示例

import java.util.concurrent.atomic.AtomicStampedReference;  public class AtomicStampedReferenceDemo {  public static void main(String[] args) {  // 實例化、取當前值和 stamp 值  final Integer initialRef = 0, initialStamp = 0;  final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp);  System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());   // compare and set  final Integer newReference = 666, newStamp = 999;  final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);  System.out.println("currentValue=" + asr.getReference()  + ", currentStamp=" + asr.getStamp()  + ", casResult=" + casResult);   // 獲取當前的值和當前的 stamp 值  int[] arr = new int[1];  final Integer currentValue = asr.get(arr);  final int currentStamp = arr[0];  System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);   // 單獨設置 stamp 值  final boolean attemptStampResult = asr.attemptStamp(newReference, 88);  System.out.println("currentValue=" + asr.getReference()  + ", currentStamp=" + asr.getStamp()  + ", attemptStampResult=" + attemptStampResult);   // 從新設置當前值和 stamp 值  asr.set(initialRef, initialStamp);  System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());   // [不推薦使用,除非搞清楚註釋的意思了] weak compare and set  // 困惑!weakCompareAndSet 這個方法最終仍是調用 compareAndSet 方法。[版本: jdk-8u191]  // 可是註釋上寫着 "May fail spuriously and does not provide ordering guarantees,  // so is only rarely an appropriate alternative to compareAndSet."  // todo 感受有多是 jvm 經過方法名在 native 方法裏面作了轉發  final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);  System.out.println("currentValue=" + asr.getReference()  + ", currentStamp=" + asr.getStamp()  + ", wCasResult=" + wCasResult);  } }

輸出結果以下:

currentValue=0, currentStamp=0
currentValue=666, currentStamp=999, casResult=true currentValue=666, currentStamp=999 currentValue=666, currentStamp=88, attemptStampResult=true currentValue=0, currentStamp=0 currentValue=666, currentStamp=999, wCasResult=true

4.4 AtomicMarkableReference 類使用示例

import java.util.concurrent.atomic.AtomicMarkableReference;  public class AtomicMarkableReferenceDemo {  public static void main(String[] args) {  // 實例化、取當前值和 mark 值  final Boolean initialRef = null, initialMark = false;  final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);  System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());   // compare and set  final Boolean newReference1 = true, newMark1 = true;  final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);  System.out.println("currentValue=" + amr.getReference()  + ", currentMark=" + amr.isMarked()  + ", casResult=" + casResult);   // 獲取當前的值和當前的 mark 值  boolean[] arr = new boolean[1];  final Boolean currentValue = amr.get(arr);  final boolean currentMark = arr[0];  System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);   // 單獨設置 mark 值  final boolean attemptMarkResult = amr.attemptMark(newReference1, false);  System.out.println("currentValue=" + amr.getReference()  + ", currentMark=" + amr.isMarked()  + ", attemptMarkResult=" + attemptMarkResult);   // 從新設置當前值和 mark 值  amr.set(initialRef, initialMark);  System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());   // [不推薦使用,除非搞清楚註釋的意思了] weak compare and set  // 困惑!weakCompareAndSet 這個方法最終仍是調用 compareAndSet 方法。[版本: jdk-8u191]  // 可是註釋上寫着 "May fail spuriously and does not provide ordering guarantees,  // so is only rarely an appropriate alternative to compareAndSet."  // todo 感受有多是 jvm 經過方法名在 native 方法裏面作了轉發  final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);  System.out.println("currentValue=" + amr.getReference()  + ", currentMark=" + amr.isMarked()  + ", wCasResult=" + wCasResult);  } }

輸出結果以下:

currentValue=null, currentMark=false
currentValue=true, currentMark=true, casResult=true currentValue=true, currentMark=true currentValue=true, currentMark=false, attemptMarkResult=true currentValue=null, currentMark=false currentValue=true, currentMark=true, wCasResult=true

5 對象的屬性修改類型原子類

5.1 對象的屬性修改類型原子類介紹

若是須要原子更新某個類裏的某個字段時,須要用到對象的屬性修改類型原子類。

  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新長整形字段的更新器
  • AtomicStampedReference :原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用於解決原子的更新數據和數據的版本號,能夠解決使用 CAS 進行原子更新時可能出現的 ABA 問題。

要想原子地更新對象的屬性須要兩步。第一步,由於對象的屬性修改類型原子類都是抽象類,因此每次使用都必須使用靜態方法 newUpdater()建立一個更新器,而且須要設置想要更新的類和屬性。第二步,更新的對象屬性必須使用 public volatile 修飾符。

上面三個類提供的方法幾乎相同,因此咱們這裏以 AtomicIntegerFieldUpdater爲例子來介紹。

5.2 AtomicIntegerFieldUpdater 類使用示例

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;  public class AtomicIntegerFieldUpdaterTest {  public static void main(String[] args) {  AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");   User user = new User("Java", 22);  System.out.println(a.getAndIncrement(user));// 22  System.out.println(a.get(user));// 23  } }  class User {  private String name;  public volatile int age;   public User(String name, int age) {  super();  this.name = name;  this.age = age;  }   public String getName() {  return name;  }   public void setName(String name) {  this.name = name;  }   public int getAge() {  return age;  }   public void setAge(int age) {  this.age = age;  }  }

輸出結果:

22
23
相關文章
相關標籤/搜索