JVM學習-CAS與原子類
阿新 • • 發佈:2021-02-05
1.CAS
CAS 即 Compare and Swap ,它體現的一種樂觀鎖的思想,比如多個執行緒要對一個共享的整型變數執 行 +1 操作:
// 需要不斷嘗試
while(true) {
int 舊值 = 共享變數 ; // 比如拿到了當前值 0
int 結果 = 舊值 + 1; // 在舊值 0 的基礎上增加 1 ,正確結果是 1
/* 這時候如果別的執行緒把共享變數改成了 5,本執行緒的正確結果 1 就作廢了,這時
compareAndSwap 返回 false,重新嘗試,直到:
compareAndSwap 返回 true,表示我本執行緒做修改的同時,別的執行緒沒有干擾
*/
//這裡其實是把共享變數的值與舊值做比較,如果相同,就可以安全的把結果寫入共享變數
if( compareAndSwap ( 舊值, 結果 )) {
// 成功,退出迴圈
}
}
CAS對共享變數可以不用加鎖。
**獲取共享變數時,為了保證該變數的可見性,需要使用 volatile 修飾。**結合 CAS 和 volatile 可以實現無 鎖併發,適用於競爭不激烈、多核 CPU 的場景下。
- 因為沒有使用 synchronized,所以執行緒不會陷入阻塞,這是效率提升的因素之一
- 但如果競爭激烈,可以想到重試必然頻繁發生,反而效率會受影響
執行緒陷入阻塞,會涉及到執行緒的上下文切換,這樣就嚴重降低了效率。
CAS 底層依賴於一個 Unsafe 類來直接呼叫作業系統底層的 CAS 指令,下面是直接使用 Unsafe 物件進 行執行緒安全保護的一個例子
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class TestCAS {
public static void main(String[] args) throws InterruptedException {
DataContainer dc = new DataContainer();
int count = 5;
Thread t1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
dc.increase();
}
});
t1.start();
t1.join();
System.out.println(dc.getData());
}
}
class DataContainer{
private volatile int data;
static final Unsafe unsafe;
static final long DATA_OFFSET;
static {
try {
// Unsafe 物件不能直接呼叫,只能通過反射獲得
Field theUnsafe =Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
}catch(NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
try {
// data 屬性在 DataContainer 物件中的偏移量,用於 Unsafe 直接訪問該屬性。
//方便定位data的地址。
DATA_OFFSET=
unsafe.objectFieldOffset(DataContainer.class.getDeclaredField("data"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
public void increase(){
int oldValue;
while(true) {
// 獲取共享變數舊值,可以在這一行加入斷點,修改 data 除錯來加深理解
oldValue = data;
// cas 嘗試修改 data 為 舊值 + 1,如果期間舊值被別的執行緒改了,返回 false
/*第一個引數當前DataContainer物件,第二個引數偏移量。
對前面兩個資訊我們知道,針對哪個欄位做compareandSwap。
後面的引數就是舊值和新值,它首先會把oldValue與共享變數的當前值做比較。
如果兩個不一致,表明修改失敗,然後我就重新進入while迴圈,直到舊值和共享變數的值一致了。
然後compareandswap才會返回true,並且新值更新到共享變數data裡去。
if條件成立,while就結束了
*/
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue + 1)) {
return;
}
}
}
public void decrease() {
int oldValue;
while(true) {
oldValue = data;
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue,
oldValue - 1)){
return;
}
}
}
2 樂觀鎖與悲觀鎖
CAS 是基於樂觀鎖的思想:樂觀的估計,不怕別的執行緒來修改共享變數,就算改了也沒關係, 我吃虧點再重試唄。
synchronized 是基於悲觀鎖的思想:悲觀的估計,得防著其它執行緒來修改共享變數,我上了鎖 你們都別想改,我改完了解開鎖,你們才有機會。
3 原子操作類
juc(java.util.concurrent)中提供了原子操作類,可以提供執行緒安全的操作,例如:AtomicInteger、 AtomicBoolean等,它們底層就是採用 CAS 技術 + volatile 來實現的。 可以使用 AtomicInteger 改寫之前的例子:
// 建立原子整數物件
private static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndIncrement(); // 獲取並且自增 i++
// i.incrementAndGet(); // 自增並且獲取 ++i
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndDecrement(); // 獲取並且自增 i++
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}