Java?CAS與JUC組件詳解
1.前言
哈嘍大家好吖,不知不覺多線程這一塊大骨頭終于快要啃完了,今天給大家分享的是CAS以及JUC相關(guān)組件,那么廢話不多說讓我們開始吧。
2.正文
2.1CAS概念
核心思想:無所并發(fā)控制
CAS(Compare And Swap)是一種基于樂觀鎖的無鎖并發(fā)控制技術(shù)。其核心邏輯可以概括為:“我認(rèn)為當(dāng)前值應(yīng)該是A,如果是,則更新為B;否則放棄或重試”。整個(gè)過程由硬件保證原子性,無需傳統(tǒng)鎖機(jī)制。
通俗來說
假設(shè)你和同事協(xié)同編輯一份共享文檔,每次保存時(shí)系統(tǒng)會(huì)檢查:當(dāng)前內(nèi)容是否和你打開時(shí)的版本一致(預(yù)期值比對(duì))。
如果一致,允許保存;否則提示“內(nèi)容已變更,請(qǐng)重新編輯”。
這個(gè)過程就是CAS的核心思想——樂觀鎖:先操作,沖突時(shí)重試,而非直接加鎖阻塞。
CAS操作的偽代碼可以拆解為以下步驟,幫助理解其原子性本質(zhì):
// 偽代碼:CAS操作的邏輯分解
public boolean compareAndSwap(MemoryAddress addr, int expectedValue, int newValue) {
// 1. 讀取內(nèi)存當(dāng)前值
int currentValue = *addr;
// 2. 比較當(dāng)前值與預(yù)期值
if (currentValue != expectedValue) {
return false; // 值已被其他線程修改,操作失敗
}
// 3. 若值未變,執(zhí)行原子性更新
*addr = newValue;
return true;
}2.2CAS兩種用途
2.2.1實(shí)現(xiàn)原子類
針對(duì)原子類,++--這樣的操作是原子的,基于CAS實(shí)現(xiàn),不涉及到加鎖。
傳統(tǒng)實(shí)現(xiàn):
private int count = 0;
public synchronized void increment() {
count++;
} 進(jìn)階實(shí)現(xiàn): (使用Java提供的原子類)
AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldValue, newValue;
do {
oldValue = count.get();
newValue = oldValue + 1;
} while (!count.compareAndSet(oldValue, newValue)); // CAS自旋
} 2.2.2實(shí)現(xiàn)自旋鎖
先回顧一個(gè)上篇文章的概念:自旋鎖是線程通過循環(huán)(自旋)不斷嘗試獲取鎖,而非立即阻塞。適用于鎖持有時(shí)間極短的場(chǎng)景。
代碼實(shí)現(xiàn):
public class CASSpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
// 獲取鎖
public void lock() {
while (!locked.compareAndSet(false, true)) {
// 自旋:直到成功將locked從false改為true
}
}
// 釋放鎖
public void unlock() {
locked.set(false);
}
} 線程競(jìng)爭(zhēng)不激烈時(shí)(如短任務(wù)),自旋鎖比系統(tǒng)鎖(如
synchronized)更高效。缺點(diǎn):長時(shí)間自旋會(huì)浪費(fèi)CPU資源(需根據(jù)場(chǎng)景權(quán)衡)。
2.3缺陷:ABA問題
ABA問題場(chǎng)景
- 線程1讀取變量值為
A。 - 線程2將值改為
B,隨后又改回A。 - 線程1執(zhí)行CAS操作,發(fā)現(xiàn)當(dāng)前值仍是
A,誤認(rèn)為未被修改過,操作成功。
通俗理解:
- 你看到自己的水杯是滿的(A),去接水時(shí)離開了一會(huì)兒。
- 期間別人喝光水(A→B)又倒?jié)M(B→A)。
- 你回來后以為水沒被喝過,直接喝下(可能喝到別人的水?。?。
這里在實(shí)際場(chǎng)景中就是非常嚴(yán)重的線程安全的問題了。
解決方案:
1. 版本號(hào)標(biāo)記(AtomicStampedReference)
為值附加一個(gè)版本號(hào)(類似“修改次數(shù)”),CAS時(shí)同時(shí)校驗(yàn)值和版本號(hào)。
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
// 線程1讀取值和版本號(hào)
int stamp = ref.getStamp();
String oldValue = ref.getReference();
// 線程2修改值并更新版本號(hào)
ref.compareAndSet("A", "B", stamp, stamp + 1);
ref.compareAndSet("B", "A", stamp + 1, stamp + 2);
// 線程1嘗試修改:雖然值還是A,但版本號(hào)已變,操作失?。?
boolean success = ref.compareAndSet(oldValue, "C", stamp, stamp + 1); 2. 狀態(tài)標(biāo)記(AtomicMarkableReference)
用布爾值標(biāo)記是否被修改過(簡(jiǎn)化版版本號(hào))。
2.4JUC組件
2.4.1Callable接口
官方解析:Callable (Java SE 17 & JDK 17)
Callable是 Java 并發(fā)包(JUC)中定義的接口,類似于Runnable,但允許線程執(zhí)行任務(wù)后返回結(jié)果,并可以拋出異常。與
Runnable的區(qū)別:
Runnable的run()沒有返回值,Callable的call()可以返回泛型結(jié)果。call()可以拋出受檢異常,run()不能。
具體案例(異步運(yùn)算1加到100):
Callable<Integer> task = () -> {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
};
FutureTask<Integer> futureTask = new FutureTask<>(task);
new Thread(futureTask).start();
// 主線程獲取結(jié)果
System.out.println("計(jì)算結(jié)果:" + futureTask.get()); // 輸出 5050通過
FutureTask包裝Callable任務(wù),啟動(dòng)線程執(zhí)行后,主線程通過futureTask.get()等待結(jié)果返回,類似“異步任務(wù)+回調(diào)”模式。
2.4.2ReentrantLock(與synchronized對(duì)比)
官方解析:ReentrantLock (Java SE 17 & JDK 17)
ReentrantLock 是 JUC 提供的顯式鎖,支持可重入性、可中斷鎖、公平鎖等特性。
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 鎖獲取方式 | 隱式(JVM 管理) | 顯式(代碼手動(dòng)加鎖/解鎖) |
| 可中斷 | 不支持 | 支持 lockInterruptibly() |
| 公平鎖 | 不支持 | 支持(構(gòu)造函數(shù)指定) |
| 條件變量(Condition) | 無 | 支持(newCondition()) |
案例:
class BankAccount {
private final ReentrantLock lock = new ReentrantLock();
private int balance = 100;
void transfer(BankAccount target, int amount) {
lock.lock();
try {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
}
} finally {
lock.unlock(); // 必須手動(dòng)釋放鎖
}
}
}
synchronized的等價(jià)實(shí)現(xiàn)是在方法簽名加synchronized關(guān)鍵字,但ReentrantLock更靈活:
- 可設(shè)置超時(shí)時(shí)間(
tryLock(1, TimeUnit.SECONDS))。- 公平鎖減少線程饑餓問題。
2.4.3Semaphore信號(hào)量
官方解析:Semaphore (Java SE 17 & JDK 17)
Semaphore 用于控制同時(shí)訪問某個(gè)資源的線程數(shù)量,類似“許可證發(fā)放”。
核心方法:
acquire():獲取許可證(若無可用則阻塞)。release():釋放許可證。
案例:(模擬停車場(chǎng))
Semaphore semaphore = new Semaphore(3); // 3 個(gè)許可證
Runnable parkAction = () -> {
try {
semaphore.acquire(); // 獲取車位
System.out.println(Thread.currentThread().getName() + " 停入車位");
Thread.sleep(2000); // 停車 2 秒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 釋放車位
System.out.println(Thread.currentThread().getName() + " 離開車位");
}
};
// 啟動(dòng) 5 輛車嘗試停車
for (int i = 0; i < 5; i++) {
new Thread(parkAction).start();
}
2.4.4CountDownLatch
官方解析:CountDownLatch (Java SE 17 & JDK 17)
CountDownLatch 是一個(gè)同步工具,允許一個(gè)或多個(gè)線程等待其他線程完成操作。
核心方法:
countDown():計(jì)數(shù)器減 1。await():阻塞直到計(jì)數(shù)器歸零。
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3); // 需要等待 3 個(gè)任務(wù)
// 資源加載任務(wù)
Runnable loadTask = () -> {
try {
Thread.sleep((long) (Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 加載完成");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 啟動(dòng) 3 個(gè)資源加載線程
new Thread(loadTask, "地圖").start();
new Thread(loadTask, "音效").start();
new Thread(loadTask, "UI").start();
// 主線程等待所有資源加載完成
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有資源加載完成,開始游戲!");
}
3.小結(jié)
今天的分享到這里就結(jié)束了,喜歡的小伙伴點(diǎn)點(diǎn)贊點(diǎn)點(diǎn)關(guān)注,你的支持就是對(duì)我最大的鼓勵(lì),大家加油!
到此這篇關(guān)于Java CAS與JUC組件的文章就介紹到這了,更多相關(guān)Java CAS與JUC組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring boot實(shí)現(xiàn)自動(dòng)輸出word文檔功能的實(shí)例代碼
這篇文章主要介紹了spring boot實(shí)現(xiàn)自動(dòng)輸出word文檔功能的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Springboot實(shí)現(xiàn)高吞吐量異步處理詳解(適用于高并發(fā)場(chǎng)景)
這篇文章主要介紹了Springboot實(shí)現(xiàn)高吞吐量異步處理詳解(適用于高并發(fā)場(chǎng)景),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Java同步鎖synchronized用法的最全總結(jié)
這篇文章主要介紹了Java同步鎖synchronized用法的最全總結(jié),需要的朋友可以參考下,文章詳細(xì)講解了Java同步鎖Synchronized的使用方法和需要注意的點(diǎn),希望對(duì)你有所幫助2023-03-03
@RequestMapping對(duì)不同參數(shù)的接收方式示例詳解
Spring?MVC框架中,@RequestMapping注解用于映射URL到控制器方法,不同的參數(shù)類型如簡(jiǎn)單參數(shù)、實(shí)體參數(shù)、數(shù)組參數(shù)、集合參數(shù)、日期參數(shù)和JSON參數(shù),本文給大家介紹@RequestMapping對(duì)不同參數(shù)的接收方式,感興趣的朋友一起看看吧2024-10-10
springboot中不能獲取post請(qǐng)求參數(shù)的解決方法
這篇文章主要介紹了springboot中不能獲取post請(qǐng)求參數(shù)的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

