Java讀源碼之LockSupport

前言

JDK版本: 1.8java

做用

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

這個類是爲擁有更高級別抽象的併發類服務的,開發中咱們不會用到這個類dom

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

瞭解LockSupport能夠幫助咱們更好理解併發,並且你們熟悉的併發中最核心的AQS類中也大量的使用了LockSupport,因此仍是有必要看一看的,至少熟悉其中的概念。this

爲何須要LockSupport

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

Thread.suspend和Thread.resume爲何被棄用

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

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

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

LockSupport會有上面的問題麼,又有哪些特色呢,讓咱們進入源碼code

源碼

類聲明和屬性

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 結束使命
     */
}
相關文章
相關標籤/搜索