1. 程式人生 > >Java-併發-鎖-LockSupport

Java-併發-鎖-LockSupport

Java-併發-鎖-LockSupport

0x01 摘要

LockSupport是用來建立鎖和其他同步類的基本執行緒阻塞原語,他的兩個主要方法park()unpark()的作用分別是阻塞執行緒和解除阻塞執行緒。本文簡要分析下他的原始碼。

0x02 原始碼解析

2.1 類定義和構造方法

// 該類很耿直,就是個獨立的 沒有什麼亂七八糟的繼承關係
public class LockSupport {
    // 私有構造方法,不能例項化LockSupport
    private LockSupport() {}
}

2.2 初始化

// Hotspot implementation via intrinsics API
// UNSAFE例項 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 { // 只有根BootStrapClassLoader載入的類才能使用這個方式初始化UNSAFE, // 否則會報異常Exception in thread "main" java.lang.SecurityException: Unsafe
UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> tk = Thread.class; // 獲取parkBlocker這個field在Thread類中的偏移位置 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); } }

2.3 重要方法

2.3.1 unpark

/**
 * 如果指定的執行緒處於park狀態,就給他個許可讓他不再阻塞
 * 否則,該執行緒的下一次park呼叫會保證不會被阻塞,但是多次呼叫就沒用,只能保證一次
 * 當然,如果執行緒沒開始執行,就沒有任何保證
 *
 * @param thread 需要被unpark的執行緒物件,傳null沒有任何作用
 */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

2.3.2 park

/**
 * 禁用當前執行緒已達到不可排程執行的目的,直到有許可 可用
 * 如果此方法時就有許可可用(在此之前已經用unpark給了本執行緒一個許可),那就會立刻消費這個許可並立刻返回;
 * 否則當前執行緒會被禁止執行緒排程,保持睡眠直到以下三種情況發生:
 * 1.某個其他的執行緒呼叫了unpark,並以當前執行緒為引數,給一個許可
 * 2.某個其他的執行緒對當前執行緒呼叫了interrupt方法,發了中斷(此時不會丟擲InterruptedException)
 * 3.The call spuriously (that is, for no reason) returns莫名奇妙的返回了?
 *
 * 因為該方法返回空,所以呼叫者不會知道是啥原因導致的返回,所以需要檢查下原因,
 * 比如查下執行緒中斷狀態
 *
 * @param blocker 負責park此執行緒的同步物件
 * @since 1.6
 */
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

/**
 * 禁用當前執行緒已達到不可排程執行的目的,直到有許可 可用
 * 如果此方法時就有許可可用(在此之前已經用unpark給了本執行緒一個許可),那就會立刻消費這個許可並立刻返回;
 * 否則當前執行緒會被禁止執行緒排程,保持睡眠直到以下四種情況發生:
 * 1.某個其他的執行緒呼叫了unpark,並以當前執行緒為引數,給一個許可
 * 2.某個其他的執行緒對當前執行緒呼叫了interrupt方法,發了中斷(此時不會丟擲InterruptedException)
 * 3.The call spuriously (that is, for no reason) returns莫名奇妙的返回了?
 * 4.指定的等待時間消耗完畢
 *
 * 因為該方法返回空,所以呼叫者不會知道是啥原因導致的返回,所以需要檢查下原因,
 * 比如查下執行緒中斷狀態
 *
 * @param blocker 負責park此執行緒的同步物件
 * @param nanos 等待時間閾值,納秒單位
 * @since 1.6
 */
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);
    }
}
// 這個方法與parkNanos類似,只不過傳入的是毫秒級的絕對時間即時間戳
public static void parkUntil(long deadline) {
    UNSAFE.park(true, deadline);
}
    
/**
 * 禁用當前執行緒已達到不可排程執行的目的,直到有許可 可用
 * 
 * 如果此方法時就有許可可用(在此之前已經用unpark給了本執行緒一個許可),那就會立刻消費這個許可並立刻返回;
 * 否則當前執行緒會被禁止執行緒排程,保持睡眠直到以下三種情況發生:
 * 1.某個其他的執行緒呼叫了unpark,並以當前執行緒為引數,給一個許可
 * 2.某個其他的執行緒對當前執行緒呼叫了interrupt方法,發了中斷(此時不會丟擲InterruptedException)
 * 3.The call spuriously (that is, for no reason) returns莫名奇妙的返回了?
 * 
 * 因為該方法返回空,所以呼叫者不會知道是啥原因導致的返回,所以需要檢查下原因,
 * 比如查下執行緒中斷狀態
 */
public static void park() {
    UNSAFE.park(false, 0L);
}

2.4 輔助方法

private static void setBlocker(Thread t, Object arg) {
    // 就算是volatile,hotspot vm 也不需要在這裡使用讀屏障
    // 將當前執行緒的parkBlocker設為arg
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

0x03 使用示例

import java.util.concurrent.locks.LockSupport;

public class LockParkDemo1 {
    private static Thread mainThread;
    public static void main(String[] args) {
        InnerThread it =  new LockParkDemo1().new InnerThread();
        Thread td = new Thread(it);
        mainThread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + " start it");
        td.start();
        System.out.println(Thread.currentThread().getName() + " block");
//        LockSupport.park(Thread.currentThread());
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + " continue");

    }
    class InnerThread implements Runnable{
        @Override
        public void run() {
            int count = 5;
            while(count>0){
                System.out.println("count=" + count);
                count--;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+" wakup others");
            LockSupport.unpark(mainThread);
        }
    }
}

程式輸出結果如下:

main start it
main block
Thread-0 wakup others
main continue

0x04 LockSupport和wait/notify區別

  • 阻塞和喚醒是對於執行緒來說的,LockSupportpark/unpark更符合這個語義,以“執行緒”作為方法的引數, 語義更清晰,使用起來也更方便;

  • wait/notify的實現使得“執行緒”的阻塞/喚醒對執行緒本身來說是被動的,要準確的控制哪個執行緒、什麼時候阻塞/喚醒很困難, 要不隨機喚醒一個執行緒(notify)要不喚醒所有的(notifyAll)。

ReentrantLock的lock就是利用了LockSupport的相關方法來使執行緒阻塞或者喚醒的。

0xFF 參考文件

Unsafe類park,unpark詳解