1. 程式人生 > >Java讀原始碼之LockSupport

Java讀原始碼之LockSupport

前言

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 結束使命
     */
}