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