(1)CAS(compare and swap) 比較並替換,比較和替換是線程併發算法時用到的一種技術
(2)CAS是原子操做,保證併發安全,而不是保證併發同步
(3)CAS是CPU的一個指令
(4)CAS是非阻塞的、輕量級的樂觀鎖
java
樂觀鎖,嚴格來講並非鎖,經過原子性來保證數據的同步,好比說數據庫的樂觀鎖,經過版本控制來實現等,因此CAS不會保證線程同步。樂觀的認爲在數據更新期間沒有其餘線程影響算法
CAS(compare and swap) 比較並替換,就是將內存值更新爲須要的值,可是有個條件,內存值必須與指望值相同。舉個例子,指望值 E、內存值M、更新值U,當E == M的時候將M更新爲U。數據庫
因爲CAS是CPU指令,咱們只能經過JNI與操做系統交互,關於CAS的方法都在sun.misc包下Unsafe的類裏 java.util.concurrent.atomic包下的原子類等經過CAS來實現原子操做。安全
/**
* Created by Dell on 2018/2/6.
*/
public class CasLock {
private static final CountDownLatch latch = new CountDownLatch(5);
private static AtomicInteger i = new AtomicInteger(0);
private static int p = 0;
public static void main(String[] args) throws InterruptedException {
long time = System.currentTimeMillis();
ExecutorService pool = Executors.newFixedThreadPool(5);
for(int j = 0; j < 5; j++) {
pool.execute(new Runnable() {
public void run() {
for(int k = 0; k < 10000; k++) {
p++; //不是原子操做
i.getAndIncrement();//調用原子類加1
}
latch.countDown();
}
});
}
latch.await();//保證全部子線程執行完成
System.out.println(System.currentTimeMillis() - time);
System.out.println("p=" + p);
System.out.println("i=" + i);
pool.shutdown();
}
}
複製代碼
輸出結果bash
"C:\Program Files\Java\jdk1.8.0_91\bin\java" ...
8
p=43204//結果不正確
i=50000
Process finished with exit code 0
複製代碼
根據結果咱們發現,因爲多線程異步進行p++操做,致使結果不正確。
爲何p++的記過不正確呢?好比兩個線程讀到p的值爲1,而後作加1操做,這時候p的值是2,而不是3 而變量i的結果倒是對的,這就要歸功於CAS,下面咱們具體看一下原子類。多線程
原子類例如AtomicInteger裏的方法都很簡單,你們看一看都能懂,咱們具體看下getAndIncrement方法。下面貼出代碼:併發
//該方法功能是Interger類型加1
public final int getAndIncrement() {
//主要看這個getAndAddInt方法
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//var1 是this指針
//var2 是地址偏移量
//var4 是自增的數值,是自增1仍是自增N
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//獲取內存值,這是內存值已是舊的,假設咱們稱做指望值E
var5 = this.getIntVolatile(var1, var2);
//compareAndSwapInt方法是重點,
//var5是指望值,var5 + var4是要更新的值
//這個操做就是調用CAS的JNI,每一個線程將本身內存裏的內存值M
//與var5指望值E做比較,若是相同將內存值M更新爲var5 + var4,不然作自旋操做
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
複製代碼
解釋一下getAndAddInt方法的流程
假設有如下情景: 1.A、B兩個線程
2.jvm主內存的值1,A、B工做內存的值爲1(工做內存會拷貝一份主內存的值)
3.當前指望值爲1,作加1操做
4.此時var5 = 1, var4 = 1,
(1)A線程將var5與工做內存值M比較,比較var5是否等於1
(2)若是相同則將工做內存值修改成var5+var4 既修改成2並同步到主內存,此時this指針裏,示例變量value的值就是2,結束循環
(3)若是不相同則其B線程修改了主內存的值,說明B線程已經先於A線程作了加1操做,A線程沒有更新成功須要繼續循環,注意此時var5更新爲新的內存值,假設當前的內存值是2,那麼此時var5 = 2, var5 + var4 = 3,重複上述步驟直到成功
app
下面是compareAndSwapInt本地方法的源碼,能夠看到使用cmpxchg指令實現CAS,在效率上有不錯的表現。異步
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
複製代碼
CAS不只是樂觀鎖,是種思想,咱們也能夠在平常項目中經過相似CAS的操做保證數據安全,但並非全部場合都適合,曾看過帖子說,能用synchronized就不要用CAS,除非遇到性能瓶頸,由於CAS會讓代碼可讀性變差,這句話看你們怎麼理解了。jvm