Java讀源碼之LockSupport

  • 2019 年 10 月 17 日
  • 筆記

前言

JDK版本: 1.8

作用

LockSupport類主要提供了park和unpark兩個native方法,用於阻塞和喚醒線程。注釋中有這麼一段:

這個類是為擁有更高級別抽象的並發類服務的,開發中我們不會用到這個類

既然只是native方法,開發中也用不到,那麼還有必要去看么?

了解LockSupport可以幫助我們更好理解並發,而且大家熟悉的並發中最核心的AQS類中也大量的使用了LockSupport,所以還是有必要看一看的,至少熟悉其中的概念。

為什麼需要LockSupport

已經知道了這個類就是阻塞喚醒,Object.wait和Object.notify,Thread.suspend和Thread.resume這兩對方法也是類似效果,那麼還有必要去看么???

Thread.suspend和Thread.resume為什麼被棄用

  • suspend將線程掛起,從運行狀態阻塞狀態,但是並不釋放所佔用的鎖
  • suspend方法至少已經滿足互斥,不可剝奪兩個死鎖的條件了
  • resume將線程解除掛起,從阻塞狀態到運行狀態,通常是等待其他任務完成, 請求與保持條件也成立了
  • 最後只差 循環等待條件 就死鎖了,這實在太危險了,一不小心就容易死鎖,而且死鎖的問題是很難排查的

Object.wait和Object.notify存在什麼問題

  • 不滿足條件時我們需要在代碼中保證拿到鎖才能調用,把線程放到等待隊列中
  • notify是從等待池中隨機放一個線程出來,當需要喚醒特定線程時,只能notifyAll

LockSupport會有上面的問題么,又有哪些特點呢,讓我們進入源碼

源碼

類聲明和屬性

package java.util.concurrent.locks;    public class LockSupport {      // 工具類,ban掉構造      private LockSupport() {}        private static final sun.misc.Unsafe UNSAFE;      // parkBlocker的內存偏移量      private static final long parkBlockerOffset;      private static final long SEED;      private static final long PROBE;      private static final long SECONDARY;      static {          try {              UNSAFE = sun.misc.Unsafe.getUnsafe();              Class<?> tk = Thread.class;              // 反射拿到Thread類中的parkBlocker屬性,然後獲取其在內存中的偏移量              parkBlockerOffset = UNSAFE.objectFieldOffset                  (tk.getDeclaredField("parkBlocker"));              SEED = UNSAFE.objectFieldOffset                  (tk.getDeclaredField("threadLocalRandomSeed"));              PROBE = UNSAFE.objectFieldOffset                  (tk.getDeclaredField("threadLocalRandomProbe"));              SECONDARY = UNSAFE.objectFieldOffset                  (tk.getDeclaredField("threadLocalRandomSecondarySeed"));          } catch (Exception ex) { throw new Error(ex); }      }    }

park()

// 最簡單的方式,但是不推薦  public static void park() {      /**       * 將當前線程掛起,是通過二元信號量,獲取許可證實現的,拿到許可證後才執行掛起       * 不是基於對象的監視器鎖,所以不需要顯示的同步       * 如果超時了,被中斷了或者unpark了就會return並且釋放許可證       * 需要注意的是和wait一樣也會因為JVM內部未知原因return,所以我們如果使用也需要放在循環內       * 第一個參數 flase代表納秒級別超時控制,此級別下第二個參數timeout為0代表無限等待       * 第一個參數 true代表毫秒級別超時控制,此級別下第二個參數timeout為0會立即返回       */      UNSAFE.park(false, 0L);  }    // 推薦方式,blocker是個輔助對象,用於跟蹤許可證的獲取,以及定位一些阻塞問題,一般情況park(this)就行  public static void park(Object blocker) {      Thread t = Thread.currentThread();      // 標記對於當前線程t,blocker正在獲取許可證,出問題通過getBlocker方法去定位      setBlocker(t, blocker);      UNSAFE.park(false, 0L);      // park操作return了,標記許可證已經釋放      setBlocker(t, null);  }    private static void setBlocker(Thread t, Object arg) {      // 通過偏移量,把給當前線程t的parkBlocker屬性賦值為arg      UNSAFE.putObject(t, parkBlockerOffset, arg);  }

相信大家已經基本了解park操作了,LockSupport還給我們提供了其他功能

// 推薦,納秒級別timeout後return  public static void parkNanos(Object blocker, long nanos) {      if (nanos > 0) {          Thread t = Thread.currentThread();          setBlocker(t, blocker);          UNSAFE.park(false, nanos);          setBlocker(t, null);      }  }    // 推薦,毫秒級別timeout後return  public static void parkUntil(Object blocker, long deadline) {      Thread t = Thread.currentThread();      setBlocker(t, blocker);      UNSAFE.park(true, deadline);      setBlocker(t, null);  }    // 不推薦,納秒級別timeout後return  public static void parkNanos(long nanos) {      if (nanos > 0)          UNSAFE.park(false, nanos);  }    // 不推薦,毫秒級別timeout後return  public static void parkUntil(long deadline) {      UNSAFE.park(true, deadline);  }

unpark

public static void unpark(Thread thread) {      if (thread != null)          //釋放線程thread的許可證,如果已經是釋放狀態那就什麼都不會發生,因為總共就1個許可,所以unpark可以先於park執行沒有任務問題          UNSAFE.unpark(thread);  }

其他方法

// 由於包權限問題從ThreadLocalRandom類中copy過來的,用於生成隨機數種子  static final int nextSecondarySeed() {      int r;      Thread t = Thread.currentThread();      if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {          r ^= r << 13;   // xorshift          r ^= r >>> 17;          r ^= r << 5;      }      else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)          r = 1; // avoid zero      UNSAFE.putInt(t, SECONDARY, r);      return r;  }

實踐

public class LockSupportTest {        public static void main(String[] args) {          AtomicBoolean flag = new AtomicBoolean(true);          Thread thread = new Thread(() -> {              Thread curr = Thread.currentThread();              System.out.println("線程1 即將被阻塞");              while (flag.get()) {                  LockSupport.park(curr);                  System.out.println("線程1 復活");              }              System.out.println("線程1 結束使命");          });          thread.start();            new Thread(() -> {              System.out.println("喚醒線程1");              flag.compareAndSet(true, false);              LockSupport.unpark(thread);          }).start();      }        /**       * 輸出:       * 線程1 即將被阻塞       * 喚醒線程1       * 線程1 復活       * 線程1 結束使命       */  }