1. CAS算法
- CAS 全稱 Compare And Swap(比較與交換),是一種無鎖算法。在不使用鎖(沒有線程被阻塞)的情況下實作多線程之間的變量同步。CAS是一種硬體對并發的支援,針對多處理器操作而設計的處理器中的一種特殊指令,用于管理對共享資料的并發通路。java.util.concurrent.atomic包中的原子類就是通過CAS來實作了樂觀鎖。
- CAS算法涉及到三個操作數:
- 需要讀寫的記憶體值 V
- 進行比較的值 A(A本質上指的是“舊值”)
- 要寫入的新值 B
- 當且僅當 V 的值等于 A 時,CAS通過原子方式用新值B來更新V的值(“比較+更新”整體是一個原子操作),否則不會執行任何操作。一般情況下,“更新”是一個不斷重試的操作。
- 有沒有可能在判斷了 V == A 之後,正準備更新它的新值的時候,被其它線程更改了 V 的值呢?不會的,因為CAS是一種原子操作,它是一種系統原語,是一條CPU的原子指令,從CPU層面保證它的原子性。當多個線程同時使用CAS操作一個變量時,隻有一個會勝出,并成功更新,其餘均會失敗,但失敗的線程并不會被挂起,僅是被告知失敗,并且允許再次嘗試,當然也允許失敗的線程放棄操作。
Java 實作 CAS 的原理:Unsafe 類 - 在 Java 中,如果一個方法是 native 的,那 Java 就不負責具體實作它,而是交給底層的 JVM 使用 C 或者 C++ 去實作。在 Java 中,有一個 Unsafe 類,它在 sun.misc 包中,它裡面是一些 native 方法,其中就有幾個關于 CAS 的方法。Unsafe 類中對 CAS 的實作是 C++ 寫的,它的具體實作和作業系統、CPU 都有關系。
CAS 實作原子操作的三大問題: - ABA問題。CAS 需要在操作值的時候檢查記憶體值是否發生變化,沒有發生變化才會更新記憶體值。但是如果記憶體值原來是 A,後來變成了 B,然後又變成了 A,那麼 CAS 進行檢查時會發現值沒有發生變化,但是實際上是有變化的。ABA 問題的解決思路就是在變量前面添加版本号,每次變量更新的時候都把版本号加一,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”。從 JDK 1.5 開始,JDK 的 atomic 包裡提供了一個類 AtomicStampedReference 類來解決 ABA 問題,具體操作封裝在 compareAndSet() 中。compareAndSet() 首先檢查目前引用和目前标志與預期引用和預期标志是否相等,如果都相等,則以原子方式将引用值和标志的值設定為給定的更新值。
- 循環時間長開銷大。CAS 多與自旋結合。自旋 CAS 操作如果長時間不成功,會導緻其一直自旋,給 CPU 帶來非常大的開銷。解決思路是讓 JVM 支援處理器提供的 pause 指令,pause 指令能讓自旋失敗時 cpu 睡眠一小段時間再繼續自旋。
- 隻能保證一個共享變量的原子操作。對一個共享變量執行操作時,CAS 能夠保證原子操作,但是對多個共享變量執行操作時,CAS 是無法保證操作的原子性的。JDK 從 1.5 開始提供了 AtomicReference 類來保證引用對象之間的原子性,可以把多個變量放在一個對象裡來進行 CAS 操作。或者使用鎖來解決這一問題,因為鎖内的臨界區代碼可以保證隻有目前線程能操作。
2. 線程安全的原子操作類
- java.util.concurrent.atomic(Java并發原子包)包裡的類基本都是使用 Unsafe 實作的包裝類。
2.1 原子更新基本類型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新長整型
- AtomicBoolean:原子更新布爾類型
package com.java.atomic;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
//AtomicInteger類中聲明了value屬性:private volatile int value;
//new AtomicInteger(10); 等價于 value = 10;
static AtomicInteger ai = new AtomicInteger(10);
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(100); //該線程先睡100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("incrementAndGet : " + ai.incrementAndGet());
}, "A").start();
new Thread(() -> {
System.out.println("get : " + ai.get());
System.out.println("getAndIncrement : " + ai.getAndIncrement());
}, "B").start();
}
/**
* 執行結果:
* get : 10
* getAndIncrement : 10
* incrementAndGet : 12
*/
}
getAndIncrement()方法實作原子操作的原理: - 第一步先取出AtomicInteger裡存儲的數值;
- 第二步對取出來的數值進行+1操作;
- 第三步調用compareAndSet方法進行原子更新操作,該方法先檢查目前AtomicInteger裡存儲的數值是否等于current,等于意味着AtomicInteger裡存儲的數值沒有被其他線程修改過,此時将AtomicInteger裡存儲的數值更新成next;如果不等,compareAndSet會傳回false,程式會再次進入for循環重新進行compareAndSet操作。
public final int getAndIncrement() {
for(;;) {
int current = get();
int next = current + 1;
if(compareAndSet(current,next)) {
return current;
}
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe隻提供了3種CAS方法,檢視AtomicBoolean源碼可以看到它是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,是以原子更新char、float、double變量也可使用類似的思路。
//Unsafe類的部分源碼:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
2.2 原子更新數組
- AtomicIntegerArray:原子更新整型數組裡的元素
- AtomicLongArray:原子更新長整型數組裡的元素
- AtomicReferenceArray:原子更新引用類型數組裡的元素
package com.java.atomic;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
static int[] array = new int[]{1, 2, 3};
//數組array通過構造器傳入,然後AtomicIntegerArray會将目前數組複制一份,
//是以當AtomicIntegerArray對内部的數組元素進行修改時,不會影響傳入的數組。
static AtomicIntegerArray aia = new AtomicIntegerArray(array);
public static void main(String[] args) {
System.out.println("索引1處的元素 = " + aia.get(1));
System.out.println("索引1處的元素 = " + aia.getAndSet(1, 10));
System.out.println("索引1處的元素 = " + aia.get(1));
System.out.println("array[1] = " + array[1]);
}
/**
* 執行結果:
* 索引1處的元素 = 2
* 索引1處的元素 = 2
* 索引1處的元素 = 10
* array[1] = 2
*/
}
2.3 原子更新引用
原子更新基本類型的AtomicInteger,隻能更新一個變量,如果要原子更新多個變量,就需要使用這個原子更新引用類型提供的類。
- AtomicReference:原子更新引用類型
- AtomicReferenceFieldUpdater:原子更新引用類型裡的字段
- AtomicMarkableReference:原子更新帶有标記位的引用類型(可以原子更新一個布爾類型的标記位和引用類型)
package com.java.atomic;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
static AtomicReference<User> ar = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("AA", 18);
ar.set(user);
User updateUser = new User("BB", 81);
boolean flag = ar.compareAndSet(user, updateUser);
System.out.println(flag); //true
System.out.println(ar.get().getName()); //BB
System.out.println(ar.get().getAge()); //81
}
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
2.4 原子更新屬性 (字段)
如果需要原子的更新某個類裡的某個字段時,需要使用原子更新字段類。
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
- AtomicLongFieldUpdater:原子更新長整型的字段的更新器
- AtomicStampedReference:原子更新帶有版本号的引用類型(該類将整數值與引用關聯起來,可用于原子的更新資料和資料的版本号,可以解決使用CAS進行原子更新時可能出現的ABA問題)
package com.java.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest {
//建立原子更新器,并設定需要更新的對象所屬的類和對象的屬性
//public abstract class AtomicIntegerFieldUpdater<T> { //...... }
//AtomicIntegerFieldUpdater是抽象類,需要使用其靜态方法newUpdater建立一個實作類對象
static AtomicIntegerFieldUpdater<User> aifu = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
public static void main(String[] args) {
User user = new User("XX", 24);
System.out.println(aifu.getAndIncrement(user)); //24
System.out.println(aifu.get(user)); //25
}
static class User {
private String name;
//被更新的字段必須使用 public volatile 修飾
public volatile int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}