1. 程式人生 > >JAVA高併發---LockSupport的學習及簡單使用

JAVA高併發---LockSupport的學習及簡單使用

JAVA高併發—LockSupport的學習及簡單使用

1、簡單介紹

   LockSupport是JDK中比較底層的類,用來建立鎖和其他同步工具類的基本執行緒阻塞原語。可以做到與join()wait()/notifyAll() 功能一樣,使執行緒自由的阻塞、釋放。
   Java鎖和同步器框架的核心AQS(AbstractQueuedSynchronizer 抽象佇列同步器),就是通過呼叫LockSupport.park()LockSupport.unpark()實現執行緒的阻塞和喚醒的。

補充:AQS定義了一套多執行緒訪問共享資源的同步器框架,許多同步類實現都依賴於它,
如常用的ReentrantLock/Semaphore/CountDownLatch...。

2、簡單原理

  LockSupport方法底層都是呼叫Unsafe的方法實現。全名sun.misc.Unsafe,該類可以直接操控記憶體,被JDK廣泛用於自己的包中,如java.nio和java.util.concurrent。但是不建議在生產環境中使用這個類。因為這個API十分不安全、不輕便、而且不穩定。

   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也不會積累。意思就是說unpark

之後,如果permit 已經變為1,之後,再執行unpark ,permit 依舊是1。下邊有例子會說到。

3、簡單例子

  以下邊的做飯例子,正常來說,做飯 之前,要有鍋有菜才能開始做飯 。具體如下:
(1)先假設已經有了 ,那隻需要買菜就可以做飯。如下,即註釋掉了買鍋的步驟:

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
      //買鍋
//      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));
//      t1.start();

      //買菜
        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));
        t2.start();
//        LockSupport.park();
//        System.out.println("鍋買回來了...");
        LockSupport.park();
        System.out.println("菜買回來 了...");
        System.out.println("開始做飯");
    }
}
class BuyGuo implements Runnable{
    private Object threadObj;
    public BuyGuo(Object threadObj) {
        this.threadObj = threadObj;
    }

    @Override
    public void run() {
        System.out.println("去買鍋...");
        LockSupport.unpark((Thread)threadObj);

    }
}
class BuyCai implements Runnable{
    private Object threadObj;
    public BuyCai(Object threadObj) {
        this.threadObj = threadObj;
    }

    @Override
    public void run() {
        System.out.println("買菜去...");
        LockSupport.unpark((Thread)threadObj);
    }
}

  執行後,可出現下面的結果:

買菜去...
菜買回來了...
開始做飯

   如上所述,可以達到阻塞主執行緒等到買完菜之後才開始做飯。這即是park()unpark() 的用法。簡單解釋一下上述的步驟:

  1. main 方法啟動後,主執行緒買菜執行緒 同時開始執行。
  2. 因為兩者同時進行,當主執行緒 走到park() 時,發現permit 還為0 ,即會等待在這裡。
  3. 買菜執行緒執行進去後,走到unpark() 會將permit 變為1
  4. 主執行緒 park() 處發現permit 已經變成1 ,就可以繼續往下執行了,同時消費掉permit ,重新變成0

   以上permit 只是park/unpark 執行的一種邏輯開關,執行的步驟大致如此。

4、注意點及思考

(1)必須將park()uppark() 配對使用才更高效。

  如果上邊也把買鍋的執行緒放開,main 方法改為如下:

       //買鍋
      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));
       t1.start();
      //買菜
        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));
        t2.start();
        LockSupport.park();
        System.out.println("鍋買回來了...");
        LockSupport.park();
        System.out.println("菜買回來了...");
        System.out.println("開始做飯");

  即呼叫了兩次park()unpark() ,發現有時候可以,有時候會使執行緒卡在那裡,然後我又換了下順序,如下:

       //買鍋
      Thread t1 = new Thread(new BuyGuo(Thread.currentThread()));
       t1.start();
          LockSupport.park();
        System.out.println("鍋買回來了...");
      //買菜
        Thread t2 = new Thread(new BuyCai(Thread.currentThread()));
        t2.start();
        LockSupport.park();
        System.out.println("菜買回來了...");
        System.out.println("開始做飯");

  原理沒有詳細去研究,不過想了想,上邊兩種其實並無區別,只是執行順序有了影響,park()unpark() 既然是成對配合使用,通過標識permit 來控制,如果像前邊那個例子那樣,出現阻塞的情況原因,我分析可能是這麼個原因:

  當買鍋的時候,通過unpark()permit 置為1,但是還沒等到外邊的main方法執行第一個park() ,買菜的執行緒又調了一次unpark(),但是這時候permit 還是從1變成了1,等回到主執行緒呼叫park()的時候,因為還有兩個park()需要執行,也就是需要兩個消費permit ,因為permit 只有1個,所以,可能會剩下一個park()卡在那裡了。

(2)使用park(Object blocker) 方法更能明確問題

  其實park() 有個過載方法park(Object blocker) ,這倆方法效果差不多,但是有blocker的可以傳遞給開發人員更多的現場資訊,可以檢視到當前執行緒的阻塞物件,方便定位問題。所以java6新增加帶blocker入參的系列park方法,替代原有的park方法。

5、與wait()/notifyAll() 的比較

LockSupportpark/unpark 方法,雖然與平時Objectwait/notify 同樣達到阻塞執行緒的效果。但是它們之間還是有區別的。

  1. 面向的物件主體不同。LockSupport() 操作的是執行緒物件,直接傳入的就是Thread ,而wait() 屬於具體物件,notifyAll() 也是針對所有執行緒進行喚醒。
  2. wait/notify 需要獲取物件的監視器,即synchronized修飾,而park/unpark 不需要獲取物件的監視器。
  3. 實現的機制不同,因此兩者沒有交集。也就是說 LockSupport 阻塞的執行緒,notify/notifyAll 沒法喚醒。但是 park 之後,同樣可以被中斷(interrupt()) !