LockSupport詳解


  我們知道,concurrent包是基於AQS (AbstractQueuedSynchronizer)框架,AQS框架借助於兩個類:Unsafe(提供CAS操作) 和 LockSupport(提供park/unpark操作)。因此,LockSupport可謂構建concurrent包的基礎之一。理解concurrent包,就從這里開始。

LockSupport 簡介

  LockSupport是一個線程阻塞工具類,所有的方法都是靜態方法,可以讓線程在任意位置阻塞,當然阻塞之后肯定得有喚醒的方法。歸根結底,LockSupport調用的Unsafe中的native代碼。

  LockSupport是用來創建鎖和其他同步類的基本線程阻塞原語。LockSupport 提供park()和unpark()方法實現阻塞線程和解除線程阻塞,LockSupport和每個使用它的線程都有一個許可(permit)關聯。permit相當於1,0的開關,默認是0,調用一次unpark就加1變成1,調用一次park會消費permit, 也就是將1變成0,同時park立即返回。再次調用park會變成block(因為permit為0了,會阻塞在這里,直到permit變為1), 這時調用unpark會把permit置為1。每個線程都有一個相關的permit, permit最多只有一個,重復調用unpark也不會積累。

  park() 和 unpark()不會有 Thread.suspendThread.resume 所可能引發的死鎖問題,由於許可的存在,調用 park 的線程和另一個試圖將其 unpark 的線程之間的競爭將保持活性。

  如果調用線程被中斷,則park方法會返回。同時park也擁有可以設置超時時間的版本。

public static void park(Object blocker); // 暫停當前線程
public static void parkNanos(Object blocker, long nanos); // 暫停當前線程,不過有超時時間的限制
public static void parkUntil(Object blocker, long deadline); // 暫停當前線程,直到某個時間
public static void park(); // 無期限暫停當前線程
public static void parkNanos(long nanos); // 暫停當前線程,不過有超時時間的限制
public static void parkUntil(long deadline); // 暫停當前線程,直到某個時間
public static void unpark(Thread thread); // 恢復當前線程
public static Object getBlocker(Thread t);

  為什么叫park呢,park英文意思為停車。我們如果把Thread看成一輛車的話,park就是讓車停下,unpark就是讓車啟動然后跑起來。

  我們可以使用它來阻塞和喚醒線程,功能和wait,notify有些相似,但是LockSupport比起wait,notify功能更強大,也好用的多。

示例

1、使用 wait,notify 阻塞喚醒線程

@Test
public void testWaitNotify() {
    Object obj = new Object();
    Thread waitThread = new Thread(() -> {
        synchronized (obj) {
            System.out.println("start wait!!!");
            try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}
            System.out.println("end wait!!!");
        }
    });
    Thread notifyThread = new Thread(() -> {
        synchronized (obj) {
            System.out.println("start notify");
            obj.notify();
            System.out.println("end notify!!!");
        }
    });
    waitThread.start();
    notifyThread.start();
}

結果如下所示:

start wait!!!
start notify
end notify!!!
end wait!!!

使用 wait,notify 來實現等待喚醒功能至少有兩個缺點:

  • 1. 由上面的例子可知,wait 和 notify 都是 Object 中的方法,在調用這兩個方法前必須先獲得鎖對象,這限制了其使用場合:只能在同步代碼塊中。
  • 2. 另一個缺點可能上面的例子不太明顯,當對象的等待隊列中有多個線程時,notify只能隨機選擇一個線程喚醒,無法喚醒指定的線程。

而使用LockSupport的話,我們可以在任何場合使線程阻塞,同時也可以指定要喚醒的線程,相當的方便。

2、使用 LockSupport 阻塞喚醒線程

@Test
public void testLockSupport() {
    Thread parkThread = new Thread(() -> {
        System.out.println("開始線程阻塞");
        LockSupport.park();
        System.out.println("結束線程阻塞");
    });
    parkThread.start();
    System.out.println("開始線程喚醒");
    LockSupport.unpark(parkThread);
    System.out.println("結束線程喚醒");
}

結果如下所示:

開始線程阻塞
開始線程喚醒
結束線程阻塞
結束線程喚醒

  LockSupport.park(); 可以用來阻塞當前線程,park是停車的意思,把運行的線程比作行駛的車輛,線程阻塞則相當於汽車停車,相當直觀。

  該方法還有個變體 LockSupport.park(Object blocker),指定線程阻塞的對象 blocker,該對象主要用來排查問題。方法 LockSupport.unpark(Thread thread) 用來喚醒線程,因為需要線程作參數,所以可以指定線程進行喚醒。

許可

  上面的這個“許可”是不能疊加的,“許可”是一次性的。

  比如線程B 連續調用了三次 unpark函數,當線程A 調用 park函數就使用掉這個“許可”,如果線程A 再次調用 park,則進入等待狀態。

  注意,unpark函數 可以先於 park 調用。比如線程B 調用 unpark函數,給線程A 發了一個“許可”,那么當線程A 調用 park 時,它發現已經有“許可”了,那么它會馬上再繼續運行。

  可能有些朋友還是不理解“許可”這個概念,我們深入HotSpot的源碼來看看。每個java線程都有一個Parker實例,Parker類是這樣定義的:

class Parker : public os::PlatformParker {  
private:  
  volatile int _counter ;  
  ...  
public:  
  void park(bool isAbsolute, jlong time);  
  void unpark();  
  ...  
}  
class PlatformParker : public CHeapObj<mtInternal> {  
  protected:  
    pthread_mutex_t _mutex [1] ;  
    pthread_cond_t  _cond  [1] ;  
    ...  
}

LockSupport就是通過控制變量 _counter 來對線程阻塞喚醒進行控制的。原理有點類似於信號量機制。

  • 當調用 park()方法時,會將 _counter 置為 0,同時判斷前值 < 1 說明前面被 unpark過,則直接退出,否則將使該線程阻塞。
  • 當調用 unpark()方法時,會將 _counter 置為 1,同時判斷前值 < 1 會進行線程喚醒,否則直接退出。
    形象的理解,線程阻塞需要消耗憑證(permit),這個憑證最多只有1個。當調用 park方法時,如果有憑證,則會直接消耗掉這個憑證然后正常退出;但是如果沒有憑證,就必須阻塞等待憑證可用;而 unpark則相反,它會增加一個憑證,但憑證最多只能有1個。
  • 為什么可以先喚醒線程后阻塞線程?
    因為 unpark獲得了一個憑證,之后調用 park因為有憑證消費,故不會阻塞。
  • 為什么喚醒兩次后阻塞兩次會阻塞線程。
    因為憑證的數量最多為 1,連續調用兩次 unpark 和 調用一次 unpark 效果一樣,只會增加一個憑證;而調用兩次 park卻需要消費兩個憑證。

總結

  正如文章開始處提到的,是不是覺得 LockSupport.park() 和 unpark() 和 object.wait() 和 notify() 很相似,那么它們有什么區別呢?
  1. 面向的主體不一樣。LockSuport 主要是針對 Thread 進行阻塞處理,可以指定阻塞隊列的目標對象,每次可以指定具體的線程喚醒。Object.wait() 是以對象為緯度,阻塞當前的線程和喚醒單個或所有線程。
  2. 實現機制不同。兩者的阻塞隊列並不交叉。也就是說 unpark 不會對 wait 起作用,notify 也不會對 park 起作用。object.notifyAll() 不能喚醒 LockSupport 的阻塞 Thread。
  LockSupport 是 JDK 中用來實現線程阻塞和喚醒的工具。使用它可以在任何場合使線程阻塞,可以指定任何線程進行喚醒,並且不用擔心阻塞和喚醒操作的順序,但要注意連續多次喚醒的效果和一次喚醒是一樣的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM