上一篇講述了CAS機制,這篇講解CAS具體操作.
什么是悲觀鎖、樂觀鎖?在java語言里,總有一些名詞看語義跟本不明白是啥玩意兒,也就總有部分面試官拿着這樣的詞來忽悠面試者,以此來找優越感,其實理解清楚了,這些詞也就唬不住人了。
-
synchronized是悲觀鎖,這種線程一旦得到鎖,其他需要鎖的線程就掛起的情況就是悲觀鎖。
-
CAS操作的就是樂觀鎖,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。
那么問題來了,什么是CAS操作?
CAS是Compare-and-swap(比較與替換)的簡寫,是一種有名的無鎖算法,在java中,我們主要分析Unsafe類,因為所有的CAS操作都是它來實現的,而在Unsafe類中這些方法也都是native方法
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);
看到上面的解釋是不是索然無味,查找了很多資料也沒完全弄明白,通過幾次驗證后,終於明白,最終可以理解成一個無阻塞多線程爭搶資源的模型。先上代碼
package com.company.reentrantLock;
import java.util.concurrent.atomic.AtomicBoolean;
public class AtomicBooleanTest implements Runnable{
public static AtomicBoolean exits = new AtomicBoolean(true);
public static void main(String[] args) {
AtomicBooleanTest abd = new AtomicBooleanTest();
Thread t1 = new Thread(abd);
Thread t2 = new Thread(abd);
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("begin run");
System.out.println("real " + exits.get());
if(exits.compareAndSet(true,false)){
System.out.println(Thread.currentThread().getName() + " " + exits.get() );
exits.set(true);
}else{
run();
}
}
}
輸出結果:
begin run
real true
Thread-1 false
begin run
real true
Thread-0 false
這里無論怎么運行,Thread-1、Thread-0都會執行if=true條件,而且還不會產生線程臟讀臟寫,這是如何做到的了,這就用到了我們的compareAndSet(boolean expect,boolean update)方法,先上圖簡單講解下程序原理,然后再分析compareAndSet作用。
這個圖中重最要的是compareAndSet(true,false)方法要拆開成compare(true)方法和Set(false)方法理解,是compare(true)是等於true后,就馬上設置共享內存為false,這個時候,其它線程無論怎么走都無法走到只有得到共享內存為true時的程序隔離方法區。 但是這種得不到狀態為true時使用遞歸算法是很耗cpu資源的,所以一般情況下,都會有線程sleep。
總結
這篇文章並沒有展開講compareAndSet底層調用的是unsafe.compareAndSwapInt方法,因為這是native方法,很多人都會展開找源碼,最后也只找到是調用CPU方法,沒講到具體用法,如果只用compareAndSet(true,false)舉例則更加簡單。 這種無阻塞式的多線程操作數據,在大並發情況下,是一筆非常可觀的性能提升,所以,如果在大並發或多線程性能要求高的情況下有更加好的技術選型,可以參考這種底層實現。
結合JMM知識,線程間共享的變量,首先在主存中會保留一份,然后每個線程的工作內存也會保留一份副本.
我們對比發現,這里的預期值就是線程保留的副本,當該線程從主存中獲取該變量值后,主存中該變量可能已經被其他線程刷新了,但是該線程工作內存中該變量卻還是原來的值,這就是所謂的預期值.
當你要CAS刷新該值的時候,如果發現線程工作內存和主存中不一致,就會失敗.如果一致,就可以更新成功