淺談Java併發編程系列(八)—— LockSupport原理剖析

LockSupport 用法簡介

LockSupport 和 CAS 是Java併發包中不少併發工具控制機制的基礎,它們底層其實都是依賴Unsafe實現。java

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也不會積累。api

park()和unpark()不會有 「Thread.suspend和Thread.resume所可能引起的死鎖」 問題,因爲許可的存在,調用 park 的線程和另外一個試圖將其 unpark 的線程之間的競爭將保持活性。安全

若是調用線程被中斷,則park方法會返回。同時park也擁有能夠設置超時時間的版本。併發

須要特別注意的一點:park 方法還能夠在其餘任什麼時候間「毫無理由」地返回,所以一般必須在從新檢查返回條件的循環裏調用此方法。從這個意義上說,park 是「忙碌等待」的一種優化,它不會浪費這麼多的時間進行自旋,可是必須將它與 unpark 配對使用才更高效。框架

三種形式的 park 還各自支持一個 blocker 對象參數。此對象在線程受阻塞時被記錄,以容許監視工具和診斷工具肯定線程受阻塞的緣由。(這樣的工具可使用方法 getBlocker(java.lang.Thread) 訪問 blocker。)建議最好使用這些形式,而不是不帶此參數的原始形式。在鎖實現中提供的做爲 blocker 的普通參數是 this。
看下線程dump的結果來理解blocker的做用。工具

線程dump結果對比

從線程dump結果能夠看出:
有blocker的能夠傳遞給開發人員更多的現場信息,能夠查看到當前線程的阻塞對象,方便定位問題。因此java6新增長帶blocker入參的系列park方法,替代原有的park方法。佈局

看一個Java docs中的示例用法:一個先進先出非重入鎖類的框架優化

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);
 
      // Block while not first in queue or cannot acquire lock
      while (waiters.peek() != current ||
             !locked.compareAndSet(false, true)) {
        LockSupport.park(this);
        if (Thread.interrupted()) // ignore interrupts while waiting
          wasInterrupted = true;
      }

      waiters.remove();
      if (wasInterrupted)          // reassert interrupt status on exit
        current.interrupt();
    }
 
    public void unlock() {
      locked.set(false);
      LockSupport.unpark(waiters.peek());
    }
  }}

LockSupport 源碼解讀

  1. LockSupport中主要的兩個成員變量:ui

// Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    private static final long parkBlockerOffset;

unsafe:全名sun.misc.Unsafe能夠直接操控內存,被JDK普遍用於本身的包中,如java.nio和java.util.concurrent。可是不建議在生產環境中使用這個類。由於這個API十分不安全、不輕便、並且不穩定。
LockSupport的方法底層都是調用Unsafe的方法實現。this

再來看parkBlockerOffset:
parkBlocker就是第一部分說到的用於記錄線程被誰阻塞的,用於線程監控和分析工具來定位緣由的,能夠經過LockSupport的getBlocker獲取到阻塞的對象。

static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }
 }

從這個靜態語句塊能夠看的出來,先是經過反射機制獲取Thread類的parkBlocker字段對象。而後經過sun.misc.Unsafe對象的objectFieldOffset方法獲取到parkBlocker在內存裏的偏移量,parkBlockerOffset的值就是這麼來的.

JVM的實現能夠自由選擇如何實現Java對象的「佈局」,也就是在內存裏Java對象的各個部分放在哪裏,包括對象的實例字段和一些元數據之類。 sun.misc.Unsafe裏關於對象字段訪問的方法把對象佈局抽象出來,它提供了objectFieldOffset()方法用於獲取某個字段相對 Java對象的「起始地址」的偏移量,也提供了getInt、getLong、getObject之類的方法可使用前面獲取的偏移量來訪問某個Java 對象的某個字段。

爲何要用偏移量來獲取對象?幹嘛不要直接寫個get,set方法。多簡單?
仔細想一想就能明白,這個parkBlocker就是在線程處於阻塞的狀況下才會被賦值。線程都已經阻塞了,若是不經過這種內存的方法,而是直接調用線程內的方法,線程是不會迴應調用的。

2.LockSupport的方法:

圖片描述

能夠看到,LockSupport中主要是park和unpark方法以及設置和讀取parkBlocker方法。

private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
  }

對給定線程t的parkBlocker賦值。

public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }

從線程t中獲取它的parkBlocker對象,即返回的是阻塞線程t的Blocker對象。

接下來主查兩類方法,一類是阻塞park方法,一類是解除阻塞unpark方法

阻塞線程

  • park()

public static void park() {
        UNSAFE.park(false, 0L);
}

調用native方法阻塞當前線程。

  • parkNanos(long nanos)

public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
}

阻塞當前線程,最長不超過nanos納秒,返回條件在park()的基礎上增長了超時返回。

  • parkUntil(long deadline)

public static void parkUntil(long deadline) {
  UNSAFE.park(true, deadline);
}

阻塞當前線程,知道deadline時間(deadline - 毫秒數)。

JDK1.6引入這三個方法對應的擁有Blocker版本。

  • park(Object blocker)

public static void park(Object blocker) {
  Thread t = Thread.currentThread();
  setBlocker(t, blocker);
  UNSAFE.park(false, 0L);
  setBlocker(t, null);
}

1) 記錄當前線程等待的對象(阻塞對象);
2) 阻塞當前線程;
3) 當前線程等待對象置爲null。

  • parkNanos(Object blocker, long nanos)

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);
  }
}

阻塞當前線程,最長等待時間不超過nanos毫秒,一樣,在阻塞當前線程的時候作了記錄當前線程等待的對象操做。

  • 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時間,相同的,也作了阻塞前記錄當前線程等待對象的操做。

喚醒線程

  • unpark(Thread thread)

public static void unpark(Thread thread) {
  if (thread != null)
      UNSAFE.unpark(thread);
}

喚醒處於阻塞狀態的線程Thread。

參考:
http://tool.oschina.net/apido...
http://ifeve.com/locksuppor-s...
http://www.jianshu.com/p/ceb8...
http://15838341661-139-com.it...

相關文章
相關標籤/搜索