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目錄中, 主要作用是掛起和喚醒線程,該工具類是創建鎖和其他同步類的基礎。
LockSupport類與每個使用它的線程都會關聯一個許可證,在默認情況下調用LockSupport類的方法的線程是不持有許可證的。
主要方法
void park()
如果調用park方法的線程已經拿到了與LockSupport關聯的許可證,則調用LockSupport.park()時會馬上返回,否則調用線程會被禁止參與線程的調度,也就是會被阻塞掛起。
public static void main(String[] args) {System.out.println("begin park");LockSupport.park();System.out.println("end park");}直接在main函數里面調用park方法,最終只會輸出begin park!,然后當前線程被掛起,這是因為在默認情況下調用線程是不持有許可證的。
在其他線程調用 unpark(Thread thread)方法并且將當前線程作為參數時,調用park方法而被阻塞的線程會返回。
另外,如果其他線程調用了阻塞線程的interrupt()方法,設置了中斷標志或者線程被虛假喚醒,則阻塞線程也會返回。
-
所以在調用park方法時最好也使用循環條件判斷方式。
-
需要注意的是,因調用park()方法而被阻塞的線程被其他線程中斷而返回時并不會拋出InterruptedException異常。
void unpark(Thread thread)
當一個線程調用unpark時,如果參數thread線程沒有持有thread與LockSupport類關聯的許可證,則讓thread線程持有。
如果thread之前因調用park()而被掛起,則調用unpark后,該線程會被喚醒。如果thread之前沒有調用park,則調用unpark方法后,再調用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);// 調用unpark方法讓thread持有許可證,然后park方法返回System.out.println("main thread begin unpark ");LockSupport.unpark(thread);}-
首先創建了一個子線程thread,子線程啟動后調用park方法,由于在默認情況下子線程沒有持有許可證,因而它會把自己掛起。
-
主線程休眠1s是為了讓主線程調用unpark方法前讓子線程輸出child thread begin park!并阻塞
-
主線程然后執行unpark方法,參數為子線程,這樣做的目的是讓子線程持有許可證,然后子線程調用的park方法就返回了。
park方法返回時不會告訴你因何種原因返回,所以調用者需要根據之前調用park方法的原因,再次檢查條件是否滿足,如果不滿足則還需要再次調用park方法。
例如,根據調用前后中斷狀態的對比就可以判斷是不是因為被中斷才返回的。
為了說明調用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");// 調用park方法,掛起自己,只有被中斷才推出循環while(!Thread.currentThread().isInterrupted()){LockSupport.park();}System.out.println("child thread unpark");});// 啟動子線程thread.start();// 主線程休眠1sTimeUnit.SECONDS.sleep(1);// 中斷子線程thread.interrupt();} }在如上代碼中,只有中斷子線程,子線程才會運行結束,如果子線程不被中斷,即使你調用unpark(thread)方法子線程也不會結束。
void parkNanos(long nanos)
和park方法類似,如果調用park方法的線程已經拿到了與LockSupport關聯的許可證,則調用LockSupport.parkNanos(long nanos)方法后會馬上返回。該方法的不同在于,如果沒有拿到許可證,則調用線程會被掛起nanos時間后修改為自動返回。
另外park方法還支持帶有blocker參數的方法void park(Object blocker)方法,當線程在沒有持有許可證的情況下調用park方法而被阻塞掛起時,這個blocker對象會被記錄到該線程內部。
使用診斷工具可以觀察線程被阻塞的原因,診斷工具是通過調用getBlocker(Thread)方法來獲取blocker對象的,所以JDK推薦我們使用帶有blocker參數的park方法,并且blocker被設置為this,這樣當在打印線程堆棧排查問題時就能知道是哪個類被阻塞了。
public class TestPark {public static void main(String[] args) {TestPark testPark = new TestPark();testPark.parkTest();}private void parkTest() {// 調用park 掛起自己LockSupport.park();} }運行代碼后,使用jstack pid命令查看線程堆棧時可以看到如下輸出結果。
使用帶blocker參數的park方法,線程堆棧可以提供更多有關阻塞對象的信息。
park(Object blocker)
來看下源碼
public static void park(Object blocker) {// 當前線程Thread t = Thread.currentThread();// 設置線程的blocker變量setBlocker(t, blocker);// 掛起線程UNSAFE.park(false, 0L);// 線程被激活后,清除blocker變量,因為一般都是在線程阻塞的時候才分析原因setBlocker(t, null);}Thread類里面有個變量volatile Object parkBlocker,用來存放park方法傳遞的blocker對象,也就是把blocker變量存放到了調用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);}其中參數deadline的時間單位為ms,該時間是從1970年到現在某一個時間點的毫秒值。這個方法和parkNanos(Object blocker, long nanos)方法的區別是,后者是從當前算等待nanos秒時間,而前者是指定一個時間點,比如需要等到2021.12.05日 12:00:00,則把這個時間點轉換為從1970年到這個時間點的總毫秒數。
示例
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 ;// 當期線程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)處,如果當前線程不是隊首或者當前鎖已經被其他線程獲取,則調用park方法掛起自己。
-
然后在代碼(2)處判斷,如果park方法是因為被中斷而返回,則忽略中斷,并且重置中斷標志,做個標記,然后再次判斷當前線程是不是隊首元素或者當前鎖是否已經被其他線程獲取,如果是則繼續調用park方法掛起自己。
-
然后在代碼(3)中,判斷標記,如果標記為true則中斷該線程,這個怎么理解呢?其實就是其他線程中斷了該線程,雖然我對中斷信號不感興趣,忽略它,但是不代表其他線程對該標志不感興趣,所以要恢復下。
總結
以上是生活随笔為你收集整理的Java Review - 并发编程_LockSupport的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - 并发编程_并
- 下一篇: Java Review - 并发编程_独