今天咱們還是給自個充充電,就不大戰禿頭老了!等沖個差不多,電死它!
今天我們就來聊一下CAS,這個玩意,太重要了,是并發包(JUC)的基礎,沒有它可以說是并發包簡直就是廢廢的,說它之前咱們先討論一下執行緒不安全的場景,并嘗試解決一下!
目的:怎么讓一個變數的快速到達200?
/**
* @author: tianjx
* @date: 2022/1/2 16:22
* @description: 執行緒不安全問題!
*/
public class CASDemo04 {
// 類的成員變數
static int data = 0;
// main方法內代碼
public static void main(String[] args) {
IntStream.range(0,5).forEach((i)->{
new Thread(()->{
IntStream.range(0,40).forEach((j)->{
data++;
});
}).start();
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(data);
}
}

哦?發現用多執行緒,竟然會造成結果不等于200!(假設A,B執行緒同時拿到data(100),A快一點,A現在作業記憶體中改成101,然后寫到主記憶體,然后B開始,先在作業記憶體改成101,然后在寫到主記憶體101,明明加了兩次,但是結果確實101!)
啊!這可怎么辦?那肯定就是加鎖吧!
方法一:那…synchronized登場吧,我們常用的!
/**
* @author: tianjx
* @date: 2022/1/2 16:39
* @description: synchronized 解決執行緒不安全
*/
public class CASDemo05 {
// 類的成員變數
static int data = 0;
// main方法內代碼
public static void main(String[] args) {
IntStream.range(0,5).forEach((i)->{
new Thread(()->{
synchronized (CASDemo05.class){
IntStream.range(0,40).forEach((j)->{
data++;
});
}
}).start();
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(data);
}
}
這個確實可以,但是我們都清楚synchronized是悲觀鎖,它會造成以下缺陷:
1、如果根據時間片來獲取鎖,加鎖,釋放鎖,再加鎖,在釋放鎖,那么就會造成頻繁的背景關系切換,執行緒一多,反而多執行緒的性能可能還不如單執行緒!
2、一個執行緒持有鎖之后,所有的執行緒都會被阻塞或者掛起!這不是假多執行緒嘛!
(其實synchronized也做過很多的優化,剛剛開始可不是重量級鎖哦!如果大家也想了解了解,請雙擊螢屏點個贊,并且關注不迷路哦!跪謝!)
方法二:lock鎖(朋友莫慌,后面會介紹)
public class CASDemo06 {
// 類的成員變數
static int data = 0;
static Lock lock = new ReentrantLock();
// main方法內代碼
public static void main(String[] args) {
IntStream.range(0,5).forEach((i)->{
new Thread(()->{
IntStream.range(0,40).forEach((j)->{
lock.lock();
data++;
lock.unlock();
});
}).start();
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(data);
}
}
方法三:AtomicInteger(朋友莫慌,后面會介紹)
/**
* @author: tianjx
* @date: 2022/1/2 16:54
* @description: Atomic
*/
public class CASDemo07 {
// 類的成員變數
static AtomicInteger atomicInteger = new AtomicInteger(0);
// main方法內代碼
public static void main(String[] args) {
IntStream.range(0,5).forEach((i)->{
new Thread(()->{
IntStream.range(0,40).forEach((j)->{
atomicInteger.incrementAndGet();
});
}).start();
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.get());
}
}
方法四:LongAdder(朋友莫慌,后面會介紹)
/**
* @author: tianjx
* @date: 2022/1/2 16:58
* @description:
*/
public class CASDemo08 {
// 類的成員變數
static LongAdder longAdder = new LongAdder();
// main方法內代碼
public static void main(String[] args) {
IntStream.range(0,5).forEach((i)->{
new Thread(()->{
IntStream.range(0,40).forEach((j)->{
longAdder.increment();
});
}).start();
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(longAdder);
}
}
Lock底層是用AQS+CAS來實作的,在高并發的場景下,比synchronized性能高的可不是一點半點AtomicInteger是JUC并發包下的,底層實作也是CAS,相對于lock不斷加鎖,釋放鎖更加優雅!LongAdder是JDK1.8之后新增的類,也是JUC并發包下的,不用說實作也是用了CAS,不過哦,他很有特點,它比上面更適合在高并發場景下,寫的次數大于讀的次數!
多執行緒要保證執行緒安全可是非常重要的,而且上面的方法多次提到了CAS,我們必須的聊聊了!(可能前奏有點多,但是上面知識可能會在作業中常用,所以就放到前面了!)
先簡單介紹下它,CAS其實就是compare and swap的縮寫,比較并交換,他是一種樂觀鎖,也是一種無鎖,認為大概率是不需要加鎖的,如果需要加鎖就進行比較并交換!
接下來我們說下原理,他又三個引數v,o,n,v表示記憶體中實際存放的值,o表示預期的值,n表示要修改的值,如果v==o,那么我們則把值改成n,如果不相等則更改失敗,回傳o!(就是這么簡單,別驚訝!JUC包下的類,大部分都呼叫的是unsafe的cas方法,可能引數或多或少,基本上都是這個的變種,八九不離十!)
我們已經了解了它并且還知道了它的原理,但是大家有沒有一種疑惑!假如又兩個執行緒AB,A執行緒發現v==o,準備更改為n,但是B執行緒來了,也發現v==o準備改成另外一個值,這不并沒有實作執行緒安全嘛!這不吹牛那?
是這樣的,自從JNI的出現,我們java也可以越過JVM,呼叫作業系統原語了,而CAS就是一種系統原語,而系統原語就是屬于作業系統原語了,作業系統原語可能是若干條指令,但是一個原語是不能被其他終端打斷的,所以并不會出現上面的問題!

最后的最后我們再聊聊CAS有什么優缺點把!
優點:采用無鎖的方式,在性能上可能會有不小的提升
缺點:1、會造成ABA問題,2、只能保證一個共享變數(不過可以進行變數合并成物件來玩)
最后的最后的最后在聊聊ABA問題!
臭名昭著的ABA問題,即第一個執行緒把A把值改成B,然后在改成A,第二個執行緒發現沒有符合期望值,那執行唄!
其實這不完全算問題,有些作業場景并不關心這個,只要最后的值相同就行!比如買了一張火車票,然后秒退,這時候火車票總數不變,不影響!但是有些場景很注重次數,可能這個就很嚴重了,假如高考試卷,有個人偷偷打開了保險箱,把試題拍了下來,第二個人來的時候是看不出來的,但是這樣就造成了泄題!
這時候我們其實加個版本號就行了,每次操作算一個版本,比較值的同時,在比較版本號就行了,具體實作的類有AtomicStampedReference等等!

參考博客:一文徹底搞懂CAS實作原理_東升的思考-CSDN博客
參考博客:CAS是什么?徹底搞懂CAS_阿杰-CSDN博客_cas
以上只是我的簡單理解,如果不足之處,請大家指出!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/401566.html
標籤:其他
