前面一篇文章中說明了Object的阻塞喚醒機制,今天咱們要講解另外一個類LockSupport,在AQS中你能看見它的身影,因此須要提早了解其實現和使用機制,便於後面深刻AQS的學習java
JDK版本號:1.8.0_171
在源碼閱讀以前但願你們先去閱讀幾遍註釋,其中介紹了LockSupport的設計,實現和使用機制,這裏進行簡單說明下:多線程
上述有些術語可能使人困惑,這裏咱們通俗點說,首先須要理解permit(許可),這裏也就是至關於一個變量標誌,有興趣可查看Hotspot源碼併發
HotSpot Parker用condition和mutex維護了一個_counter變量,park時,變量_counter置爲0,unpark時,變量_counter置爲1
連續兩次調用park操做,變量不會變成2,仍是1,也就是說的不能疊加,你能夠本身寫代碼驗證,由於維護的是一個變量標識更新,因此park和unpark的調用沒有前後順序限制:dom
簡單示例代碼以下:ide
Thread test = new Thread(new Runnable() { @Override public void run() { System.out.println("start"); LockSupport.park(this);// _counter爲0,阻塞 System.out.println("end"); } }); test.start(); Thread.sleep(3000); System.out.println("ready notify"); // 線程對應的_counter置爲1,同時喚醒阻塞的線程,喚醒的線程消耗掉1置爲0 LockSupport.unpark(test);
在AbstractQueuedSynchronizer中使用了LockSupport實現線程阻塞和喚醒操做,因此有必要先進行了解,怎麼經過LockSupport實現FIFO互斥鎖呢?源碼註釋處已經提供了思路,非隊首線程或者不能更新鎖標識的都須要被阻塞,仍是挺巧妙的,能夠好好理解理解工具
public class FIFOMutex { public static void main(String[] args) { FIFOMutex lock = new FIFOMutex(); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+"111"); lock.lock(); System.out.println(Thread.currentThread()+"111"); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+"222"); lock.lock(); System.out.println(Thread.currentThread()+"222"); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+"333"); lock.lock(); System.out.println(Thread.currentThread()+"333"); } }).start(); Thread.sleep(1000); lock.unlock(); Thread.sleep(1000); lock.unlock(); } 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 // 非隊首線程或者CAS獲取不到鎖標識則進行阻塞 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()); } }
常量部分經過CAS來完成操做,沒什麼須要多說的,簡單理解就好,不是重點學習
// Hotspot implementation via intrinsics API private static final sun.misc.Unsafe UNSAFE; 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; 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); } }
空的私有構造方法,不能被外部實例化ui
private LockSupport() {} // Cannot be instantiated.
大量調用了UNSAFE的native方法,有興趣的能夠去找HotSpot源碼來深刻學習,咱們這裏僅作了解使用便可this
park相關方法中被調用,記錄阻塞的對象,也就是監視和阻斷工具查緣由時保存的對象線程
private static void setBlocker(Thread t, Object arg) { // Even though volatile, hotspot doesn't need a write barrier here. UNSAFE.putObject(t, parkBlockerOffset, arg); }
簡單理解爲喚醒對應的thread線程是不正確的,實際上,即便thread線程未調用park操做阻塞這裏unpark操做也是能夠進行的,使得thread線程的permit處於可用狀態,那麼以後thread線程調用park線程將不會被阻塞,由於permit可用,參考前言寫些代碼多理解理解
public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
在permit處於不可用狀態時,阻塞當前線程,同時可傳入blocker信息,同時注意被喚醒條件有如下三種:
被喚醒的緣由不會被返回,因此須要調用方自行檢查是什麼緣由
public static void park() { UNSAFE.park(false, 0L); } public static void park(Object blocker) { Thread t = Thread.currentThread(); // 這個地方在阻塞前保存了blocker信息 setBlocker(t, blocker); UNSAFE.park(false, 0L); // 被喚醒以後被將blocker信息置空 setBlocker(t, null); }
在permit處於不可用狀態時,阻塞當前線程nanos毫秒,同時可傳入blocker信息,喚醒機制和park()相似,除了多了一個超時條件,固然這裏是超時自動喚醒的機制
public static void parkNanos(long nanos) { if (nanos > 0) UNSAFE.park(false, 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); } }
在permit處於不可用狀態時,阻塞當前線程到deadline時間點,同時可傳入blocker信息,與parkNanos相似
public static void parkUntil(long deadline) { UNSAFE.park(true, deadline); } public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(true, deadline); setBlocker(t, null); }
獲取線程t的blocker對象信息,也就是被阻塞前經過setBlocker(t, blocker)傳入的對象信息
public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); return UNSAFE.getObjectVolatile(t, parkBlockerOffset); }
這個方法是因爲多線程隨機數生成器ThreadLocalRandom的package訪問權限限制不能被這個包下的類使用,複製了一份實現出來,在StampedLock中被使用,有興趣能夠去了解,之後會在StampedLock的源碼中進行說明
/** * Returns the pseudo-randomly initialized or updated secondary seed. * Copied from ThreadLocalRandom due to package access restrictions. */ 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; }
那麼LockSupport的阻塞喚醒機制和Object的阻塞喚醒機制有什麼區別呢?
本文分析了LockSupport的使用和源碼,簡單說明了Hotspot源碼中對應的實現機制,方便各位理解,本質上而言仍是很好理解的,其實對於咱們而言更重要的在於使用,在線程阻塞喚醒機制上的使用須要你們多理解理解,下篇文章咱們就開始進行AQS的源碼學習了,固然要好好理解下LockSupport
以上內容若有問題歡迎指出,筆者驗證後將及時修正,謝謝