LockSupport 線程工具類有啥用?
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
內(nèi)容大綱

LockSupport基本概念
LockSupport是線程工具類,主要作用是阻塞和喚醒線程,底層實(shí)現(xiàn)依賴Unsafe,同時(shí)它還是鎖和其他同步類實(shí)現(xiàn)的基礎(chǔ),LockSupport提供兩類靜態(tài)函數(shù)分別是park和unpark,即阻塞與喚醒線程,下面是兩段代碼示例public static void main(String[] agrs) throws InterruptedException {
Thread th = new Thread(() -> {
//阻塞當(dāng)前線程
LockSupport.park();
System.out.println("子線程執(zhí)行---------");
});
th.start();
//睡眠2秒
Thread.sleep(2000);
System.out.println("主線程執(zhí)行---------");
//喚醒線程
LockSupport.unpark(th);
}
}
輸出結(jié)果:
主線程執(zhí)行---------
子線程執(zhí)行---------
th調(diào)用LockSupport.park()阻塞,主線程睡眠2秒后,執(zhí)行LockSupport.unpark(th)喚醒th線程,先阻塞后喚醒非常好理解,接下來讀者們?cè)倏聪旅娴氖纠?/section>public static void main(String[] agrs) throws InterruptedException {
Thread th = new Thread(() -> {
//喚醒當(dāng)前線程
LockSupport.unpark(Thread.currentThread());
//阻塞當(dāng)前線程
LockSupport.park();
System.out.println("子線程執(zhí)行---------");
});
th.start();
//睡眠2秒
Thread.sleep(2000);
System.out.println("主線程執(zhí)行---------");
}
輸出結(jié)果:
子線程執(zhí)行---------
主線程執(zhí)行---------
th線程,再阻塞th線程,最終th線程沒有被阻塞,這是為什么?下面LockSupport的設(shè)計(jì)思路會(huì)為讀者們解開疑惑,并更進(jìn)一步明確是park和unpark的語義(從廣義上來說park和unpark代表阻塞和喚醒)。設(shè)計(jì)思路
LockSupport的設(shè)計(jì)思路是通過許可證來實(shí)現(xiàn)的,就像汽車上高速公路,入口處要獲取通行卡,出口處要交出通行卡,如果沒有通行卡你就無法出站,當(dāng)然你可以選擇補(bǔ)一張通行卡。LockSupport會(huì)為使用它的線程關(guān)聯(lián)一個(gè)許可證(permit)狀態(tài),permit的語義「是否擁有許可」,0代表否,1代表是,默認(rèn)是0。LockSupport.unpark:指定線程關(guān)聯(lián)的permit直接更新為1,如果更新前的permit<1,喚醒指定線程LockSupport.park:當(dāng)前線程關(guān)聯(lián)的permit如果>0,直接把permit更新為0,否則阻塞當(dāng)前線程

線程 A執(zhí)行LockSupport.park,發(fā)現(xiàn)permit為0,未持有許可證,阻塞線程A線程 B執(zhí)行LockSupport.unpark(入?yún)⒕€程A),為A線程設(shè)置許可證,permit更新為1,喚醒線程A線程 B流程結(jié)束線程 A被喚醒,發(fā)現(xiàn)permit為1,消費(fèi)許可證,permit更新為0線程 A執(zhí)行臨界區(qū)線程 A流程結(jié)束
unpark的語義明確為「使線程持有許可證」,park的語義明確為「消費(fèi)線程持有的許可」,所以unpark與park的執(zhí)行順序沒有強(qiáng)制要求,只要控制好使用的線程即可,unpark=>park執(zhí)行流程如下
permit默認(rèn)是0,線程A執(zhí)行LockSupport.unpark,permit更新為1,線程A持有許可證線程 A執(zhí)行LockSupport.park,此時(shí)permit是1,消費(fèi)許可證,permit更新為0執(zhí)行臨界區(qū) 流程結(jié)束
park注意點(diǎn),因park阻塞的線程不僅僅會(huì)被unpark喚醒,還可能會(huì)被線程中斷(Thread.interrupt)喚醒,而且不會(huì)拋出InterruptedException異常,所以建議在park后自行判斷線程中斷狀態(tài),來做對(duì)應(yīng)的業(yè)務(wù)處理。優(yōu)點(diǎn)
LockSupport來做線程的阻塞與喚醒(線程間協(xié)同工作),因?yàn)樗邆淙缦聝?yōu)點(diǎn)以線程為操作對(duì)象更符合阻塞線程的直觀語義 操作更精準(zhǔn),可以準(zhǔn)確地喚醒某一個(gè)線程( notify隨機(jī)喚醒一個(gè)線程,notifyAll喚醒所有等待的線程)無需競爭鎖對(duì)象(以線程作為操作對(duì)象),不會(huì)因競爭鎖對(duì)象產(chǎn)生死鎖問題 unpark與park沒有嚴(yán)格的執(zhí)行順序,不會(huì)因執(zhí)行順序引起死鎖問題,比如「Thread.suspend和Thread.resume」沒按照嚴(yán)格順序執(zhí)行,就會(huì)產(chǎn)生死鎖
LockSupport還提供了park的重載函數(shù),提升靈活性void parkNanos(long nanos):增加了超時(shí)機(jī)制void parkUntil(long deadline):加入超時(shí)機(jī)制(指定到某個(gè)時(shí)間點(diǎn),1970年到指定時(shí)間點(diǎn)的毫秒數(shù))void park(Object blocker):設(shè)置blocker對(duì)象,當(dāng)線程沒有許可證被阻塞時(shí),該對(duì)象會(huì)被記錄到該線程的內(nèi)部,方便后續(xù)使用診斷工具進(jìn)行問題排查void parkNanos(Object blocker, long nanos):設(shè)置blocker對(duì)象,加入超時(shí)機(jī)制void parkUntil(Object blocker, long deadline):設(shè)置blocker對(duì)象,加入超時(shí)機(jī)制(指定到某個(gè)時(shí)間點(diǎn),1970年到指定時(shí)間點(diǎn)的毫秒數(shù))
blocker對(duì)象,至于超時(shí)根據(jù)業(yè)務(wù)場景選擇實(shí)踐
LockSupport來完成一道阿里經(jīng)典的多線程協(xié)同工作面試題。3個(gè)獨(dú)立的線程,一個(gè)只會(huì)輸出A,一個(gè)只會(huì)輸出B,一個(gè)只會(huì)輸出C,在三個(gè)線程啟動(dòng)的情況下,請(qǐng)用合理的方式讓他們按順序打印ABCABC。準(zhǔn)備 3個(gè)線程,分別固定打印A、B、C線程輸出完 A、B、C后需要阻塞等待喚醒額外準(zhǔn)備第 4個(gè)線程,作為另外3個(gè)線程的調(diào)度器,有序的控制3個(gè)線程執(zhí)行
public static void main(String[] agrs) throws InterruptedException {
LockSupportMain lockSupportMain = new LockSupportMain();
//定義線程t1、t2、t3執(zhí)行的函數(shù)方法
Consumer<String> consumer = str -> {
while (true) {
//線程消費(fèi)許可證,并傳入blocker,方便后續(xù)排查問題
LockSupport.park(lockSupportMain);
//防止線程是因中斷操作喚醒
if (Thread.currentThread().isInterrupted()){
throw new RuntimeException("線程被中斷,異常結(jié)束");
}
System.out.println(Thread.currentThread().getName() + ":" + str);
}
};
/**
* 定義分別輸出A、B、C的線程
*/
Thread t1 = new Thread(() -> {
consumer.accept("A");
},"T1");
Thread t2 = new Thread(() -> {
consumer.accept("B");
},"T2");
Thread t3 = new Thread(() -> {
consumer.accept("C");
},"T3");
/**
* 定義調(diào)度線程
*/
Thread dispatch = new Thread(() -> {
int i=0;
try {
while (true) {
if((i%3)==0) {
//線程t1設(shè)置許可證,并喚醒線程t1
LockSupport.unpark(t1);
}else if((i%3)==1) {
//線程t2設(shè)置許可證,并喚醒線程t2
LockSupport.unpark(t2);
}else {
//線程t3設(shè)置許可證,并喚醒線程t3
LockSupport.unpark(t3);
}
i++;
TimeUnit.MILLISECONDS.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//啟動(dòng)相關(guān)線程
t1.start();
t2.start();
t3.start();
dispatch.start();
}
輸出內(nèi)容:
T1:A
T2:B
T3:C
T1:A
T2:B
T3:C
T1:A
T2:B
T3:C
Synchronized、ReentrantLock來完成這個(gè)功能。另外,關(guān)注公眾號(hào)Java技術(shù)棧,在后臺(tái)回復(fù):面試,可以獲取我整理的 Java 多線程系列面試題和答案,非常齊全。





關(guān)注Java技術(shù)??锤喔韶?/strong>

評(píng)論
圖片
表情

