併發程式設計面試必備:JUC 中的 Atomic 原子類總結

SnailClimb發表於2018-10-31

個人覺得這一節掌握基本的使用即可!

本節思維導圖:

併發程式設計面試必備:JUC 中的 Atomic 原子類總結

1 Atomic 原子類介紹

Atomic 翻譯成中文是原子的意思。在化學上,我們知道原子是構成一般物質的最小單位,在化學反應中是不可分割的。在我們這裡 Atomic 是指一個操作是不可中斷的。即使是在多個執行緒一起執行的時候,一個操作一旦開始,就不會被其他執行緒干擾。

所以,所謂原子類說簡單點就是具有原子/原子操作特徵的類。

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

JUC 原子類概覽

根據操作的資料型別,可以將JUC包中的原子類分為4類

基本型別

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

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

陣列型別

使用原子的方式更新陣列裡的某個元素

  • AtomicIntegerArray:整形陣列原子類
  • AtomicLongArray:長整形陣列原子類
  • AtomicReferenceArray :引用型別陣列原子類

引用型別

  • AtomicReference:引用型別原子類
  • AtomicStampedRerence:原子更新引用型別裡的欄位原子類
  • AtomicMarkableReference :原子更新帶有標記位的引用型別

物件的屬性修改型別

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

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

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() 方法是一個本地方法,這個方法是用來拿到“原來的值”的記憶體地址,返回值是 valueOffset。另外 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:引用型別原子類
  • AtomicStampedRerence:原子更新引用型別裡的欄位原子類
  • 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
複製程式碼

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
複製程式碼

相關文章