Java Review - 并发编程_LockSupport
文章目錄
- 概述
- 主要方法
- void park()
- void unpark(Thread thread)
- 小示例 park() & unpark(Thread thread)
- void parkNanos(long nanos)
- park(Object blocker)
- void parkNanos(Object blocker, long nanos)
- void parkUntil(Object blocker, long deadline)
- 示例
概述
位于rt.jar包的java.util.concurrent.locks目錄中, 主要作用是掛起和喚醒線程,該工具類是創(chuàng)建鎖和其他同步類的基礎(chǔ)。
LockSupport類與每個使用它的線程都會關(guān)聯(lián)一個許可證,在默認情況下調(diào)用LockSupport類的方法的線程是不持有許可證的。
主要方法
void park()
如果調(diào)用park方法的線程已經(jīng)拿到了與LockSupport關(guān)聯(lián)的許可證,則調(diào)用LockSupport.park()時會馬上返回,否則調(diào)用線程會被禁止參與線程的調(diào)度,也就是會被阻塞掛起。
public static void main(String[] args) {System.out.println("begin park");LockSupport.park();System.out.println("end park");}直接在main函數(shù)里面調(diào)用park方法,最終只會輸出begin park!,然后當(dāng)前線程被掛起,這是因為在默認情況下調(diào)用線程是不持有許可證的。
在其他線程調(diào)用 unpark(Thread thread)方法并且將當(dāng)前線程作為參數(shù)時,調(diào)用park方法而被阻塞的線程會返回。
另外,如果其他線程調(diào)用了阻塞線程的interrupt()方法,設(shè)置了中斷標志或者線程被虛假喚醒,則阻塞線程也會返回。
-
所以在調(diào)用park方法時最好也使用循環(huán)條件判斷方式。
-
需要注意的是,因調(diào)用park()方法而被阻塞的線程被其他線程中斷而返回時并不會拋出InterruptedException異常。
void unpark(Thread thread)
當(dāng)一個線程調(diào)用unpark時,如果參數(shù)thread線程沒有持有thread與LockSupport類關(guān)聯(lián)的許可證,則讓thread線程持有。
如果thread之前因調(diào)用park()而被掛起,則調(diào)用unpark后,該線程會被喚醒。如果thread之前沒有調(diào)用park,則調(diào)用unpark方法后,再調(diào)用park方法,其會立刻返回。修改代碼如下。
public static void main(String[] args) {System.out.println("begin park");LockSupport.unpark(Thread.currentThread());LockSupport.park();System.out.println("end park");}小示例 park() & unpark(Thread thread)
public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{System.out.println("child thread begin to park");LockSupport.park();System.out.println("child thread unpark");});// 啟動子線程thread.start();// 主線程休眠1sTimeUnit.SECONDS.sleep(1);// 調(diào)用unpark方法讓thread持有許可證,然后park方法返回System.out.println("main thread begin unpark ");LockSupport.unpark(thread);}-
首先創(chuàng)建了一個子線程thread,子線程啟動后調(diào)用park方法,由于在默認情況下子線程沒有持有許可證,因而它會把自己掛起。
-
主線程休眠1s是為了讓主線程調(diào)用unpark方法前讓子線程輸出child thread begin park!并阻塞
-
主線程然后執(zhí)行unpark方法,參數(shù)為子線程,這樣做的目的是讓子線程持有許可證,然后子線程調(diào)用的park方法就返回了。
park方法返回時不會告訴你因何種原因返回,所以調(diào)用者需要根據(jù)之前調(diào)用park方法的原因,再次檢查條件是否滿足,如果不滿足則還需要再次調(diào)用park方法。
例如,根據(jù)調(diào)用前后中斷狀態(tài)的對比就可以判斷是不是因為被中斷才返回的。
為了說明調(diào)用park方法后的線程被中斷后會返回,我們修改上面的例子代碼,刪除LockSupport.unpark(thread);,然后添加thread.interrupt();,
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/12/5 8:53* @mark: show me the code , change the world*/ public class LockSupportDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{System.out.println("child thread begin to park");// 調(diào)用park方法,掛起自己,只有被中斷才推出循環(huán)while(!Thread.currentThread().isInterrupted()){LockSupport.park();}System.out.println("child thread unpark");});// 啟動子線程thread.start();// 主線程休眠1sTimeUnit.SECONDS.sleep(1);// 中斷子線程thread.interrupt();} }在如上代碼中,只有中斷子線程,子線程才會運行結(jié)束,如果子線程不被中斷,即使你調(diào)用unpark(thread)方法子線程也不會結(jié)束。
void parkNanos(long nanos)
和park方法類似,如果調(diào)用park方法的線程已經(jīng)拿到了與LockSupport關(guān)聯(lián)的許可證,則調(diào)用LockSupport.parkNanos(long nanos)方法后會馬上返回。該方法的不同在于,如果沒有拿到許可證,則調(diào)用線程會被掛起nanos時間后修改為自動返回。
另外park方法還支持帶有blocker參數(shù)的方法void park(Object blocker)方法,當(dāng)線程在沒有持有許可證的情況下調(diào)用park方法而被阻塞掛起時,這個blocker對象會被記錄到該線程內(nèi)部。
使用診斷工具可以觀察線程被阻塞的原因,診斷工具是通過調(diào)用getBlocker(Thread)方法來獲取blocker對象的,所以JDK推薦我們使用帶有blocker參數(shù)的park方法,并且blocker被設(shè)置為this,這樣當(dāng)在打印線程堆棧排查問題時就能知道是哪個類被阻塞了。
public class TestPark {public static void main(String[] args) {TestPark testPark = new TestPark();testPark.parkTest();}private void parkTest() {// 調(diào)用park 掛起自己LockSupport.park();} }運行代碼后,使用jstack pid命令查看線程堆棧時可以看到如下輸出結(jié)果。
使用帶blocker參數(shù)的park方法,線程堆棧可以提供更多有關(guān)阻塞對象的信息。
park(Object blocker)
來看下源碼
public static void park(Object blocker) {// 當(dāng)前線程Thread t = Thread.currentThread();// 設(shè)置線程的blocker變量setBlocker(t, blocker);// 掛起線程UNSAFE.park(false, 0L);// 線程被激活后,清除blocker變量,因為一般都是在線程阻塞的時候才分析原因setBlocker(t, null);}Thread類里面有個變量volatile Object parkBlocker,用來存放park方法傳遞的blocker對象,也就是把blocker變量存放到了調(diào)用park方法的線程的成員變量里面。
void parkNanos(Object blocker, long nanos)
相比park(Object blocker) 方法多了個超時時間。
void parkUntil(Object blocker, long deadline)
public static void parkUntil(Object blocker, long deadline) {Thread t = Thread.currentThread();setBlocker(t, blocker);UNSAFE.park(true, deadline);setBlocker(t, null);}其中參數(shù)deadline的時間單位為ms,該時間是從1970年到現(xiàn)在某一個時間點的毫秒值。這個方法和parkNanos(Object blocker, long nanos)方法的區(qū)別是,后者是從當(dāng)前算等待nanos秒時間,而前者是指定一個時間點,比如需要等到2021.12.05日 12:00:00,則把這個時間點轉(zhuǎn)換為從1970年到這個時間點的總毫秒數(shù)。
示例
import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/12/5 9:42* @mark: show me the code , change the world*/ public class FIFOMutex {private final AtomicBoolean locked = new AtomicBoolean(false);private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();public void lock(){boolean wasInterrupted = false ;// 當(dāng)期線程Thread current = Thread.currentThread();waiters.add(current);// 1 只有隊首的線程可以獲取鎖while(waiters.peek() != current || !locked.compareAndSet(false,true)){LockSupport.park(this);if (Thread.interrupted()) { // 2wasInterrupted = true;}}waiters.remove();if (wasInterrupted){ // 3current.interrupt();}}public void unlock(){locked.set(false);LockSupport.unpark(waiters.peek());}}這是一個先進先出的鎖,也就是只有隊列的首元素可以獲取鎖。
-
在代碼(1)處,如果當(dāng)前線程不是隊首或者當(dāng)前鎖已經(jīng)被其他線程獲取,則調(diào)用park方法掛起自己。
-
然后在代碼(2)處判斷,如果park方法是因為被中斷而返回,則忽略中斷,并且重置中斷標志,做個標記,然后再次判斷當(dāng)前線程是不是隊首元素或者當(dāng)前鎖是否已經(jīng)被其他線程獲取,如果是則繼續(xù)調(diào)用park方法掛起自己。
-
然后在代碼(3)中,判斷標記,如果標記為true則中斷該線程,這個怎么理解呢?其實就是其他線程中斷了該線程,雖然我對中斷信號不感興趣,忽略它,但是不代表其他線程對該標志不感興趣,所以要恢復(fù)下。
總結(jié)
以上是生活随笔為你收集整理的Java Review - 并发编程_LockSupport的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - 并发编程_并
- 下一篇: Java Review - 并发编程_独