線程阻塞喚醒工具 LockSupport使用詳解
LockSupport 簡(jiǎn)介
LockSupport 是 Java 并發(fā)編程中一個(gè)非常重要的組件,我們熟知的并發(fā)組件 Lock、線程池、CountDownLatch 等都是基于 AQS 實(shí)現(xiàn)的,而 AQS 內(nèi)部控制線程阻塞和喚醒又是通過(guò) LockSupport 來(lái)實(shí)現(xiàn)的。
從該類的注釋上也可以發(fā)現(xiàn),它是一個(gè)控制線程阻塞和喚醒的工具,與以往的不同是它解決了曾經(jīng) wait()、notify()、await()、signal() 的局限。
回顧 synchronized 和 Lock
我們知道 Java 中實(shí)現(xiàn)并發(fā)安全通常會(huì)通過(guò)這兩種加鎖的方式,對(duì)于 synchronized 加鎖的方式,如果我們想要控制線程的阻塞和喚醒是通過(guò)鎖對(duì)象的 wait() 和 notify() 方法,以下面循環(huán)交替打印 AB 為例
int status = 2;
public static void main(String[] args) throws InterruptedException {
TestSync obj = new TestSync();
new Thread(() -> {
synchronized (obj){
while (true){
if(obj.status == 1){
obj.wait();
}
System.out.println("A");
obj.status = 1;
TimeUnit.SECONDS.sleep(1);
obj.notify();
}
}
}).start();
new Thread(() -> {
synchronized (obj){
while (true){
if(obj.status == 2){
obj.wait();
}
System.out.println("B");
obj.status = 2;
TimeUnit.SECONDS.sleep(1);
obj.notify();
}
}
}).start();
}
如果我們使用 Lock 實(shí)現(xiàn)類,上述代碼幾乎是一樣的,只是先獲取 Condition 對(duì)象
Condition condition = lock.newCondition();
把 obj.wait() 換成 condition.await(), obj.notify() 換成 condition.signal() 即可。
LockSupport 和 synchronized 和 Lock 的阻塞方式對(duì)比
| 技術(shù) | 阻塞喚醒方式 | 局限 |
|---|---|---|
| synchronized | 使用鎖對(duì)象的 wait()、notify() | 1. 只能用在 synchronized 包裹的同步代碼塊中 2. 必須先 wait() 才能 notify() |
| Lock | 使用 condition 的 await()、signal() | 1. 只能用在 lock 鎖住的代碼塊中 2. 必須先 await() 才能 signal() |
| LockSupport | park()、unpark(Thread t) | 沒有限制 |
LockSupport 的使用
下面代碼中,我們使用 LockSupport 去阻塞和喚醒線程,我們可以多次嘗試,LockSupport 的 park() 和 unpark() 方法沒有先后順序的限制,也不需要捕獲異常,也沒有限制要在什么代碼塊中才能使用。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("A");
LockSupport.park();
System.out.println("被喚醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
LockSupport.unpark(t1);
}).start();
}
LockSupport 注意事項(xiàng)
許可證提前發(fā)放
從該類的注釋中我們可以看到這個(gè)類存儲(chǔ)了使用它的線程的一個(gè)許可證,當(dāng)調(diào)用 park() 方法的時(shí)候會(huì)判斷當(dāng)前線程的許可證是否存在,如果存在將直接放行,否則就阻塞。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("A");
LockSupport.park();//不會(huì)阻塞
System.out.println("被喚醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
System.out.println("先調(diào)用 unpark()");
LockSupport.unpark(t1);
},"t2").start();
}
看這個(gè)代碼示例,這里我們?cè)?t2 中先讓線程 t1 unpark(), 然后在 t1 中調(diào)用 park(), 結(jié)果并不會(huì)阻塞 t1 線程。因?yàn)樵?t2 中調(diào)用 LockSupport.unpark(t1); 的時(shí)候相當(dāng)于給 t1 提前準(zhǔn)備好了許可證。
許可證不會(huì)累計(jì)
LockSupport.unpark(t1); 無(wú)論調(diào)用多少次,t1 的通行證只有一個(gè),當(dāng)在 t1 中調(diào)用兩次 park() 方法時(shí)線程依然會(huì)被阻塞。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("A");
LockSupport.park();
LockSupport.park();
System.out.println("被喚醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
System.out.println("先調(diào)用 unpark()");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
},"t2").start();
}
以上述代碼為例,t1 將被阻塞。
LockSupport 底層實(shí)現(xiàn)
觀察源碼發(fā)現(xiàn) park() 和 unpark() 最底下調(diào)用的是 native() 方法,源碼在 C++ 中實(shí)現(xiàn)
@IntrinsicCandidate public native void park(boolean isAbsolute, long time); @IntrinsicCandidate public native void unpark(Object thread);
對(duì),這只是個(gè)標(biāo)題,卷不動(dòng)了,不去看 C/C++ 了。。。。
結(jié)語(yǔ)
LockSupport 是 Java 并發(fā)編程中非常重要的組件,這是我們下一步閱讀 AQS(AbstractQueuedSynchronizer) 源碼的基礎(chǔ)??傊覀冎灰涀∷强刂凭€程阻塞和喚醒的工具,并且知道它與其他阻塞喚醒方式的區(qū)別即可。
以上就是線程阻塞喚醒工具 LockSupport使用詳解的詳細(xì)內(nèi)容,更多關(guān)于喚醒 LockSupport線程阻塞的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot+Thymeleaf+ECharts實(shí)現(xiàn)大數(shù)據(jù)可視化(基礎(chǔ)篇)
本文主要介紹了SpringBoot+Thymeleaf+ECharts實(shí)現(xiàn)大數(shù)據(jù)可視化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧<BR>2022-06-06
Intellij IDEA下Spring Boot熱切換配置
這篇文章主要介紹了Intellij IDEA下Spring Boot熱切換配置,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07
@ComponentScan在spring中無(wú)效的原因分析及解決方案
這篇文章主要介紹了@ComponentScan在spring中無(wú)效的原因分析及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringBoot yml配置文件調(diào)用過(guò)程解析
這篇文章主要介紹了SpringBoot yml配置文件調(diào)用過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Mybatis的sql語(yǔ)句執(zhí)行異常后打印到日志問(wèn)題
文章介紹了一種Mybatis異常日志打印方案,主要通過(guò)Mybatis攔截器獲取執(zhí)行的sql語(yǔ)句,并利用ThreadLocal存儲(chǔ),以避免多線程下的sql語(yǔ)句覆蓋問(wèn)題,當(dāng)異常發(fā)生時(shí),從ThreadLocal中取出sql語(yǔ)句并打印到單獨(dú)的日志文件中,方便數(shù)據(jù)恢復(fù),該方案經(jīng)過(guò)壓力測(cè)試2024-10-10
springboot調(diào)用webservice-soap接口的實(shí)現(xiàn)
接口協(xié)議目前廣泛使用的有http協(xié)議和RPC協(xié)議和webservice,本文主要介紹了springboot調(diào)用webservice-soap接口的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
java實(shí)現(xiàn)學(xué)生宿舍系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生宿舍系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

