JDK源碼那些事兒之LockSupport

前面一篇文章中說明了Object的阻塞喚醒機制,今天咱們要講解另外一個類LockSupport,在AQS中你能看見它的身影,因此須要提早了解其實現和使用機制,便於後面深刻AQS的學習java

前言

JDK版本號:1.8.0_171

在源碼閱讀以前但願你們先去閱讀幾遍註釋,其中介紹了LockSupport的設計,實現和使用機制,這裏進行簡單說明下:多線程

  • 每一個使用LockSupport的線程都有一個permit(許可),假如permit可用則不進行阻塞。permit不可用時使用unpark可使得其處於可用狀態,注意,這裏permit是不能疊加的,也就是說只有一個
  • park和unpark提供了線程阻塞和線程喚醒操做,park支持中斷響應,unpark操做能夠在park以前調用,這樣至關於先讓permit處於可用狀態, 那麼再調用park就不會進行阻塞
  • park操做支持blocker對象參數,線程阻塞時會記錄該對象,以容許監視和診斷工具肯定線程被阻塞的緣由,註釋中推薦使用這種方式
  • LockSupport是被設計用來建立高級同步器工具類,對於大多數併發控制程序來講用處不大

上述有些術語可能使人困惑,這裏咱們通俗點說,首先須要理解permit(許可),這裏也就是至關於一個變量標誌,有興趣可查看Hotspot源碼併發

HotSpot Parker用condition和mutex維護了一個_counter變量,park時,變量_counter置爲0,unpark時,變量_counter置爲1

連續兩次調用park操做,變量不會變成2,仍是1,也就是說的不能疊加,你能夠本身寫代碼驗證,由於維護的是一個變量標識更新,因此park和unpark的調用沒有前後順序限制:dom

  • 變量_counter爲0(默認),若是調用park,則線程會被阻塞,若是調用unpark,則變量會置爲1,喚醒被阻塞的線程(注意,阻塞的線程喚醒後會消耗掉又將變量置爲0)
  • 變量_counter爲1,若是調用park,則線程不會被阻塞,可是變量會被置爲0,若是調用unpark,則變量仍是1

簡單示例代碼以下: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);

FIFOMutex

在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

setBlocker

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

unpark

簡單理解爲喚醒對應的thread線程是不正確的,實際上,即便thread線程未調用park操做阻塞這裏unpark操做也是能夠進行的,使得thread線程的permit處於可用狀態,那麼以後thread線程調用park線程將不會被阻塞,由於permit可用,參考前言寫些代碼多理解理解

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

park

在permit處於不可用狀態時,阻塞當前線程,同時可傳入blocker信息,同時注意被喚醒條件有如下三種:

  • 其餘線程對當前線程調用unpark
  • 其餘線程中斷當前線程的執行
  • 不合邏輯的調用返回(這個不是很理解,有大神能夠評論解釋下)

被喚醒的緣由不會被返回,因此須要調用方自行檢查是什麼緣由

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

parkNanos

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

parkUntil

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

getBlocker

獲取線程t的blocker對象信息,也就是被阻塞前經過setBlocker(t, blocker)傳入的對象信息

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

nextSecondarySeed

這個方法是因爲多線程隨機數生成器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不須要先進入對應的同步鎖,可是Object的wait和notify須要先經過synchronized獲取鎖才能使用
  • LockSupport沒有被侷限在當前線程中,能夠參考使用示例,而Object的wait和notify,在沒有退出同步代碼塊以前,這個鎖實際上仍是當前線程佔用的,不論是否執行了wait和notify,只有在退出了同步代碼塊,這個鎖纔會真正的被釋放

總結

本文分析了LockSupport的使用和源碼,簡單說明了Hotspot源碼中對應的實現機制,方便各位理解,本質上而言仍是很好理解的,其實對於咱們而言更重要的在於使用,在線程阻塞喚醒機制上的使用須要你們多理解理解,下篇文章咱們就開始進行AQS的源碼學習了,固然要好好理解下LockSupport

以上內容若有問題歡迎指出,筆者驗證後將及時修正,謝謝

相關文章
相關標籤/搜索