柚子快報(bào)邀請(qǐng)碼778899分享:Java之線程篇六
柚子快報(bào)邀請(qǐng)碼778899分享:Java之線程篇六
目錄
CAS?
CAS偽代碼
CAS的應(yīng)用
實(shí)現(xiàn)原子類?
實(shí)現(xiàn)自旋鎖
CAS的ABA問(wèn)題
ABA問(wèn)題導(dǎo)致BUG的例子?
相關(guān)面試題
synchronized原理
synchronized特性?
加鎖過(guò)程
相關(guān)面試題
Callable
相關(guān)面試題
JUC的常見(jiàn)類
ReentrantLock
ReentrantLock 和 synchronized 的區(qū)別:
原子類
信號(hào)量
相關(guān)面試題
CAS?
CAS: 全稱Compare and swap,字面意思:”比較并交換“,一個(gè) CAS 涉及到以下操作:
我們假設(shè)內(nèi)存中的原數(shù)據(jù)V,舊的預(yù)期值A(chǔ),需要修改的新值B。 1. 比較 A 與 V 是否相等。(比較) 2. 如果比較相等,將 B 寫(xiě)入 V。(交換) 3. 返回操作是否成功。
CAS偽代碼
下面寫(xiě)的代碼不是原子的
,
真實(shí)的
CAS
是一個(gè)原子的硬件指令完成的
.
這個(gè)偽代碼只是輔助理解
CAS
的工作流程。
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
? &address = swapValue;
? ? ? return true;
? }
? return false;
}
CAS其實(shí)是一個(gè)cpu指令,單個(gè)的cpu指令,是原子的?。?!
就可以使用CAS完成一些操作,進(jìn)一步代替“加鎖”;
基于CAS實(shí)現(xiàn)線程安全的方式也稱為“無(wú)鎖化編程”。
優(yōu)點(diǎn):保證線程安全,同時(shí)避免阻塞,影響效率;
缺點(diǎn):代碼會(huì)變復(fù)雜,不好理解;只能夠適用特定場(chǎng)景,不如加鎖方式更普遍。
CAS的應(yīng)用
實(shí)現(xiàn)原子類?
標(biāo)準(zhǔn)庫(kù)中提供了 java.util.concurrent.atomic 包, 里面的類都是基于這種方式來(lái)實(shí)現(xiàn)的.? 典型的就是 AtomicInteger 類. 其中的 getAndIncrement 相當(dāng)于 i++ 操作.
偽代碼實(shí)現(xiàn)
class AtomicInteger {
? ?private int value;
? ?public int getAndIncrement() {
? ? ? ?int oldValue = value;
? ? ? ?while ( CAS(value, oldValue, oldValue+1) != true) {
? ? ? ? ? ?oldValue = value;
? ? ? }
? ? ? ?return oldValue;
? }
}
實(shí)現(xiàn)自旋鎖
偽代碼實(shí)現(xiàn)
public class SpinLock {
? ?private Thread owner = null;
? ?public void lock(){
? ? ? ?// 通過(guò) CAS 看當(dāng)前鎖是否被某個(gè)線程持有.
? ? ? ?// 如果這個(gè)鎖已經(jīng)被別的線程持有, 那么就自旋等待.
? ? ? ?// 如果這個(gè)鎖沒(méi)有被別的線程持有, 那么就把 owner 設(shè)為當(dāng)前嘗試加鎖的線程.
? ? ? ?while(!CAS(this.owner, null, Thread.currentThread())){
? ? ? }
? }
? ?public void unlock (){
? ? ? ?this.owner = null;
? }
}
CAS的ABA問(wèn)題
ABA問(wèn)題即:
假設(shè)有兩個(gè)線程t1和t2,有一個(gè)共享變量num,初值為A,接下來(lái)t1想使用CAS把num改為Z,那么就需要先讀取num的值,記錄到oldNum變量中,然后使用CAS判斷當(dāng)前num的值是否為A,如果為A,則改為Z。
但是在t1執(zhí)行上述操作之前,t2線程可能把num的值從A改為B,又從B改為A。
那么此時(shí),線程t1無(wú)法區(qū)分當(dāng)前這個(gè)變量始終是A,還是經(jīng)歷了一個(gè)變化過(guò)程。
ABA問(wèn)題導(dǎo)致BUG的例子?
假設(shè)你要去ATM取款機(jī)取錢(qián),余額有1000,要取款500,但是取款的時(shí)候ATM機(jī)卡了一下,所以你按了兩下,假設(shè)ATM取款機(jī)按CAS方式工作,雖然你按了兩次,但是你取出的是500,不過(guò),如果恰巧這個(gè)時(shí)候有人給你轉(zhuǎn)了500,這個(gè)時(shí)候,你按的第2下取款,使用CAS方式會(huì)發(fā)現(xiàn)余額還是1000,那么此時(shí)就會(huì)余額沒(méi)有改變,你最后會(huì)取出1000.
解決方法:引入版本號(hào)等方式
給要修改的值, 引入版本號(hào). 在 CAS 比較數(shù)據(jù)當(dāng)前值和舊值的同時(shí), 也要比較版本號(hào)是否符合預(yù)期.? CAS 操作在讀取舊值的同時(shí), 也要讀取版本號(hào).? 真正修改的時(shí)候,? 如果當(dāng)前版本號(hào)和讀到的版本號(hào)相同, 則修改數(shù)據(jù), 并把版本號(hào) + 1. 如果當(dāng)前版本號(hào)高于讀到的版本號(hào). 就操作失敗(認(rèn)為數(shù)據(jù)已經(jīng)被修改過(guò)了).
相關(guān)面試題
1) 講解下你自己理解的 CAS 機(jī)制
全稱 Compare and swap, 即 "比較并交換". 相當(dāng)于通過(guò)一個(gè)原子的操作, 同時(shí)完成 "讀取內(nèi)存, 比較是否相等, 修改內(nèi)存" 這三個(gè)步驟. 本質(zhì)上需要 CPU 指令的支撐
2) ABA問(wèn)題怎么解決?
給要修改的數(shù)據(jù)引入版本號(hào). 在 CAS 比較數(shù)據(jù)當(dāng)前值和舊值的同時(shí), 也要比較版本號(hào)是否符合預(yù)期.? 如果發(fā)現(xiàn)當(dāng)前版本號(hào)和之前讀到的版本號(hào)一致, 就真正執(zhí)行修改操作, 并讓版本號(hào)自增; 如果發(fā)現(xiàn)當(dāng)前版本號(hào)比之前讀到的版本號(hào)大, 就認(rèn)為操作失敗.
synchronized原理
synchronized特性?
1. 開(kāi)始時(shí)是樂(lè)觀鎖, 如果鎖沖突頻繁, 就轉(zhuǎn)換為悲觀鎖. 2. 開(kāi)始是輕量級(jí)鎖實(shí)現(xiàn), 如果鎖被持有的時(shí)間較長(zhǎng), 就轉(zhuǎn)換成重量級(jí)鎖.? 3. 實(shí)現(xiàn)輕量級(jí)鎖的時(shí)候大概率用到的自旋鎖策略 4. 是一種不公平鎖 5. 是一種可重入鎖 6. 不是讀寫(xiě)鎖
加鎖過(guò)程
JVM 將 synchronized 鎖分為 無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖 狀態(tài)。會(huì)根據(jù)情況,進(jìn)行依次升級(jí)。?
1)偏向鎖
第一個(gè)嘗試加鎖的線程, 優(yōu)先進(jìn)入偏向鎖狀態(tài).? 偏向鎖不是真的 "加鎖", 只是給對(duì)象頭中做一個(gè) "偏向鎖的標(biāo)記", 記錄這個(gè)鎖屬于哪個(gè)線程.? 如果后續(xù)沒(méi)有其他線程來(lái)競(jìng)爭(zhēng)該鎖, 那么就不用進(jìn)行其他同步操作了(避免了加鎖解鎖的開(kāi)銷) 如果后續(xù)有其他線程來(lái)競(jìng)爭(zhēng)該鎖(剛才已經(jīng)在鎖對(duì)象中記錄了當(dāng)前鎖屬于哪個(gè)線程了, 很容易識(shí)別當(dāng)前申請(qǐng)鎖的線程是不是之前記錄的線程), 那就取消原來(lái)的偏向鎖狀態(tài), 進(jìn)入一般的輕量級(jí)鎖狀態(tài).? 偏向鎖本質(zhì)上相當(dāng)于 "延遲加鎖" . 能不加鎖就不加鎖, 盡量來(lái)避免不必要的加鎖開(kāi)銷.? 但是該做的標(biāo)記還是得做的, 否則無(wú)法區(qū)分何時(shí)需要真正加鎖.
2)輕量級(jí)鎖
隨著其他線程進(jìn)入競(jìng)爭(zhēng), 偏向鎖狀態(tài)被消除, 進(jìn)入輕量級(jí)鎖狀態(tài)(自適應(yīng)的自旋鎖).? 此處的輕量級(jí)鎖就是通過(guò) CAS 來(lái)實(shí)現(xiàn).
通過(guò) CAS 檢查并更新一塊內(nèi)存 (比如 null => 該線程引用) 如果更新成功, 則認(rèn)為加鎖成功 如果更新失敗, 則認(rèn)為鎖被占用, 繼續(xù)自旋式的等待(并不放棄 CPU).?
3)重量級(jí)鎖
如果競(jìng)爭(zhēng)進(jìn)一步激烈, 自旋不能快速獲取到鎖狀態(tài), 就會(huì)膨脹為重量級(jí)鎖 此處的重量級(jí)鎖就是指用到內(nèi)核提供的 mutex .? 執(zhí)行加鎖操作, 先進(jìn)入內(nèi)核態(tài).? 在內(nèi)核態(tài)判定當(dāng)前鎖是否已經(jīng)被占用 如果該鎖沒(méi)有占用, 則加鎖成功, 并切換回用戶態(tài).? 如果該鎖被占用, 則加鎖失敗. 此時(shí)線程進(jìn)入鎖的等待隊(duì)列, 掛起. 等待被操作系統(tǒng)喚醒.? 經(jīng)歷了一系列的滄海桑田, 這個(gè)鎖被其他線程釋放了, 操作系統(tǒng)也想起了這個(gè)掛起的線程, 于是喚醒這個(gè)線程, 嘗試重新獲取鎖.?
鎖消除
編譯器+JVM 判斷鎖是否可消除. 如果可以, 就直接消除。
例如:?jiǎn)尉€程環(huán)境下。?
鎖粗化
一段邏輯中如果出現(xiàn)多次加鎖解鎖, 編譯器 + JVM 會(huì)自動(dòng)進(jìn)行鎖的粗化. ?
相關(guān)面試題
1)什么是偏向鎖?
偏向鎖不是真的加鎖
,
而只是在鎖的對(duì)象頭中記錄一個(gè)標(biāo)記
(
記錄該鎖所屬的線程
).
如果沒(méi)有其他線程參與競(jìng)爭(zhēng)鎖,
那么就不會(huì)真正執(zhí)行加鎖操作
,
從而降低程序開(kāi)銷
.
一旦真的涉及到其他的線程競(jìng)爭(zhēng),
再取消偏向鎖狀態(tài)
,
進(jìn)入輕量級(jí)鎖狀態(tài)
.
Callable
Callable 和 Runnable 相對(duì), 都是描述一個(gè) "任務(wù)". Callable 描述的是帶有返回值的任務(wù),? Runnable 描述的是不帶返回值的任務(wù).? Callable 通常需要搭配 FutureTask 來(lái)使用. FutureTask 用來(lái)保存 Callable 的返回結(jié)果. 因?yàn)?Callable 往往是在另一個(gè)線程中執(zhí)行的, 啥時(shí)候執(zhí)行完并不確定.? FutureTask 就可以負(fù)責(zé)這個(gè)等待結(jié)果出來(lái)的工作?
理解FutureTask
想象去吃麻辣燙
.
當(dāng)餐點(diǎn)好后
,
后廚就開(kāi)始做了
.
同時(shí)前臺(tái)會(huì)給你一張
"
小票
" .
這個(gè)小票就是
FutureTask.
后面我們可以隨時(shí)憑這張小票去查看自己的這份麻辣燙做出來(lái)了沒(méi)
.
代碼示例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo30 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 定義了任務(wù).
Callable
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
// 把任務(wù)放到線程中進(jìn)行執(zhí)行.
FutureTask
Thread t = new Thread(futureTask);
t.start();
// 此處的 get 就能獲取到 callable 里面的返回結(jié)果.
// 由于線程是并發(fā)執(zhí)行的. 執(zhí)行到主線程的 get 的時(shí)候, t 線程可能還沒(méi)執(zhí)行完.
// 沒(méi)執(zhí)行完的話, get 就會(huì)阻塞.
System.out.println(futureTask.get());
}
}
相關(guān)面試題
介紹一下Callable是什么
Callable 是一個(gè) interface . 相當(dāng)于把線程封裝了一個(gè) "返回值". 方便程序猿借助多線程的方式計(jì)算結(jié)果.? Callable 和 Runnable 相對(duì), 都是描述一個(gè) "任務(wù)". Callable 描述的是帶有返回值的任務(wù),? Runnable 描述的是不帶返回值的任務(wù). Callable 通常需要搭配 FutureTask 來(lái)使用. FutureTask 用來(lái)保存 Callable 的返回結(jié)果. 因?yàn)?Callable 往往是在另一個(gè)線程中執(zhí)行的, 啥時(shí)候執(zhí)行完并不確定.? FutureTask 就可以負(fù)責(zé)這個(gè)等待結(jié)果出來(lái)的工作.
JUC的常見(jiàn)類
JUC,是java.util.concurrent的縮寫(xiě)。
ReentrantLock
可重入互斥鎖. 和 synchronized 定位類似, 都是用來(lái)實(shí)現(xiàn)互斥效果, 保證線程安全.? ReentrantLock 也是可重入鎖. "Reentrant" 這個(gè)單詞的原意就是 "可重入".
ReentrantLock的用法
lock(): 加鎖, 如果獲取不到鎖就死等.? trylock(超時(shí)時(shí)間): 加鎖, 如果獲取不到鎖, 等待一定的時(shí)間之后就放棄加鎖.? unlock(): 解鎖
ReentrantLock 和 synchronized 的區(qū)別:
synchronized 是一個(gè)關(guān)鍵字, 是 JVM 內(nèi)部實(shí)現(xiàn)的(大概率是基于 C++ 實(shí)現(xiàn)). ReentrantLock 是標(biāo)準(zhǔn)庫(kù)的一個(gè)類, 在 JVM 外實(shí)現(xiàn)的(基于 Java 實(shí)現(xiàn)).? synchronized 使用時(shí)不需要手動(dòng)釋放鎖. ReentrantLock 使用時(shí)需要手動(dòng)釋放. 使用起來(lái)更靈活,?但是也容易遺漏 unlock.? synchronized 在申請(qǐng)鎖失敗時(shí), 會(huì)死等. ReentrantLock 可以通過(guò) trylock 的方式等待一段時(shí)間就放棄.? synchronized 是非公平鎖, ReentrantLock 默認(rèn)是非公平鎖. 可以通過(guò)構(gòu)造方法傳入一個(gè) true 開(kāi)啟公平鎖模式.?
更強(qiáng)大的喚醒機(jī)制. synchronized 是通過(guò) Object 的 wait / notify 實(shí)現(xiàn)等待-喚醒. 每次喚醒的是一個(gè)隨機(jī)等待的線程. ReentrantLock 搭配 Condition 類實(shí)現(xiàn)等待-喚醒, 可以更精確控制喚醒某個(gè)指定的線程.
// ReentrantLock 的構(gòu)造方法
public ReentrantLock(boolean fair) {
? ?sync = fair ? new FairSync() : new NonfairSync();
}
?如何選擇使用哪個(gè)鎖?
鎖競(jìng)爭(zhēng)不激烈的時(shí)候, 使用 synchronized, 效率更高, 自動(dòng)釋放更方便.? 鎖競(jìng)爭(zhēng)激烈的時(shí)候, 使用 ReentrantLock, 搭配 trylock 更靈活控制加鎖的行為, 而不是死等.? 如果需要使用公平鎖, 使用 ReentrantLock.
原子類
原子類內(nèi)部用的是 CAS 實(shí)現(xiàn),所以性能要比加鎖實(shí)現(xiàn) i++ 高很多。原子類有以下幾個(gè) AtomicBoolean AtomicInteger AtomicIntegerArray AtomicLong AtomicReference AtomicStampedReference?
ExecutorService和Executors
ExecutorService 表示一個(gè)線程池實(shí)例.? Executors 是一個(gè)工廠類, 能夠創(chuàng)建出幾種不同風(fēng)格的線程池.? ExecutorService 的 submit 方法能夠向線程池中提交若干個(gè)任務(wù).?
代碼示例
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
? ?@Override
? ?public void run() {
? ? ? ?System.out.println("hello");
? }
});
Executors 創(chuàng)建線程池的幾種方式:
newFixedThreadPool: 創(chuàng)建固定線程數(shù)的線程池 newCachedThreadPool: 創(chuàng)建線程數(shù)目動(dòng)態(tài)增長(zhǎng)的線程池. newSingleThreadExecutor: 創(chuàng)建只包含單個(gè)線程的線程池.? newScheduledThreadPool: 設(shè)定 延遲時(shí)間后執(zhí)行命令,或者定期執(zhí)行命令. 是進(jìn)階版的 Timer.?
Executors 本質(zhì)上是 ThreadPoolExecutor 類的封裝.?
信號(hào)量
信號(hào)量(Semaphore), 用來(lái)表示 "可用資源的個(gè)數(shù)". 本質(zhì)上就是一個(gè)計(jì)數(shù)器. Semaphore 的 PV 操作中的加減計(jì)數(shù)器操作都是原子的, 可以在多線程環(huán)境下直接使用.?
acquire
方法表示申請(qǐng)資源
(P
操作
), release
方法表示釋放資源
(V
操作
)
?代碼示例
import java.util.concurrent.Semaphore;
public class Demo24 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(4);
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
semaphore.acquire();
System.out.println("P 操作");
}
}
運(yùn)行結(jié)果
相關(guān)面試題
1) 線程同步的方式有哪些?
synchronized, ReentrantLock, Semaphore 等都可以用于線程同步.
2) 為什么有了 synchronized 還需要 juc 下的 lock?
以 juc 的 ReentrantLock 為例,? synchronized 使用時(shí)不需要手動(dòng)釋放鎖. ReentrantLock 使用時(shí)需要手動(dòng)釋放. 使用起來(lái)更 靈活, synchronized 在申請(qǐng)鎖失敗時(shí), 會(huì)死等. ReentrantLock 可以通過(guò) trylock 的方式等待一段時(shí) 間就放棄.? synchronized 是非公平鎖, ReentrantLock 默認(rèn)是非公平鎖. 可以通過(guò)構(gòu)造方法傳入一個(gè) true 開(kāi)啟公平鎖模式.? synchronized 是通過(guò) Object 的 wait / notify 實(shí)現(xiàn)等待-喚醒. 每次喚醒的是一個(gè)隨機(jī)等待的 線程. ReentrantLock 搭配 Condition 類實(shí)現(xiàn)等待-喚醒, 可以更精確控制喚醒某個(gè)指定的線 程.?
3) AtomicInteger 的實(shí)現(xiàn)原理是什么?
基于 CAS 機(jī)制. 偽代碼如下: ?
class AtomicInteger {
private int value;
public int getAndIncrement() {
? ? int oldValue = value;
? ? while ( CAS(value, oldValue, oldValue+1) != true) {
? ? ? ? oldValue = value;
? ? }
? ? return oldValue;
}
}
柚子快報(bào)邀請(qǐng)碼778899分享:Java之線程篇六
精彩內(nèi)容
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。