JUC 包下工具類,它的名字叫 LockSupport !你造麼?
前言
LockSupport 是 JUC 中常用的一個工具類,主要作用是掛起和喚醒執行緒。在閱讀 JUC 原始碼中經常看到,所以很有必要了解一下。
公眾號:liuzhihangs ,記錄工作學習中的技術、開發及原始碼筆記;時不時分享一些生活中的見聞感悟。歡迎大佬來指導!
介紹
基本執行緒阻塞原語建立鎖和其他同步類。Basic thread blocking primitives for creating locks and other synchronization classes.
LockSupport 類每個使用它的執行緒關聯一個許可(在意義上的Semaphore類)。 如果許可可用,呼叫 park 將立即返回,並在此過程中消費它; 否則可能阻塞。如果許可不是可用,可以呼叫 unpark 使得許可可用。(但與Semaphore不同,許可不能累積。最多有一個。)
方法 park 和 unpark 提供了阻塞的有效手段和解鎖執行緒不會遇到死鎖問題,而 Thread.suspend 和 Thread.resume 是不能用於這種目的:因為許可的存在,一個執行緒呼叫 park 和另一個執行緒試圖 unpark 它之間的競爭將保持活性。 此外,如果呼叫者執行緒被中斷,park 將返回,並且支援設定超時。 該 park 方法也可能返回在其他任何時間,“毫無理由”,因此通常必須在一個迴圈中呼叫的返回後重新檢查條件。 在這個意義上park作為“忙碌等待”不會浪費太多的時間自旋的優化,但必須以配對 unpark 使用。
這三種形式的 park 還支援 blocker 物件引數。而執行緒被阻塞時是允許使用監測和診斷工具,以確定執行緒被阻塞的原因。(診斷工具可以使用getBlocker(Thread) 方法 。)同時推薦使用帶有 blocker 引數的 park方法,通常做法是 blocker 被設定為 this 。
上面的意思總結下來個人理解是:
- 許可(permit)的上限是1,也就是說只有 0 或 1 。
- park: 沒有許可的時候,permit 為 0 ,呼叫 park 會阻塞;有許可的時候,permit 為 1 , 呼叫 park 會扣除一個許可,然後返回。
- unpark:沒有許可的時候,permit 為 0 ,呼叫 unpark 會增加一個許可,因為許可上限是 1 , 所以呼叫多次也只會為 1 個。
- 執行緒初始的時候是沒有許可的。
- park 的當前執行緒如果被中斷,會立即返回,並不會丟擲中斷異常。
- park 方法的呼叫一般要放在一個迴圈判斷體裡面。
大概如圖所示:
下面是原始碼註釋中的案例:
/** * FIFO 獨佔鎖 */ class FIFOMutex { 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 // 不在佇列頭,或者鎖被佔用,則阻塞, 就是隻有佇列頭的可以獲得鎖 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()); } }
驗證
執行緒初始有沒有許可?
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("開始執行……");
LockSupport.park();
System.out.println("LockSupport park 之後……");
}
}
- 執行後會發現,程式碼在 park 處阻塞。說明,執行緒初始是沒有許可的。
新增許可並消耗許可
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("開始執行……");
LockSupport.unpark(Thread.currentThread());
System.out.println("執行 - park");
LockSupport.park();
System.out.println("LockSupport park 之後……");
}
}
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執行緒 " + Thread.currentThread().getName() + "開始執行 park");
LockSupport.park(this);
System.out.println("執行緒 " + Thread.currentThread().getName() + "執行 park 結束");
}
});
thread.start();
// 保證 上面執行緒先執行,然後再主執行緒
Thread.sleep(5000);
System.out.println("開始執行 unpark(thread)");
LockSupport.unpark(thread);
Thread.sleep(5000);
System.out.println("執行 unpark(thread) 結束");
}
}
通過上面示例可以看出:
- 執行 unpark 可以進行給予指定執行緒一個證書。
- 執行緒當前被 park 阻塞,此時給予證書之後, park 會消耗證書,然後繼續執行。
許可上限為 1
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("unpark 1次");
LockSupport.unpark(Thread.currentThread());
System.out.println("unpark 2次");
LockSupport.unpark(Thread.currentThread());
System.out.println("執行 - park 1 次");
LockSupport.park();
System.out.println("執行 - park 2 次");
LockSupport.park();
System.out.println("LockSupport park 之後……");
}
}
- 執行緒阻塞,可以看出 permit 只能有一個
中斷可以使 park 繼續執行並不會丟擲異常
public class LockSupportTest {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執行緒 " + Thread.currentThread().getName() + "開始執行 park");
LockSupport.park(this);
System.out.println("執行緒 " + Thread.currentThread().getName() + "執行 park 結束");
System.out.println("執行緒 " + Thread.currentThread().getName() + "開始執行 park 第二次");
LockSupport.park(this);
System.out.println("執行緒 " + Thread.currentThread().getName() + "執行 park 第二次 結束");
}
});
try {
thread.start();
// 保證 上面執行緒先執行,然後再主執行緒
Thread.sleep(5000);
System.out.println("開始執行 unpark(thread)");
// LockSupport.unpark(thread);
thread.interrupt();
Thread.sleep(5000);
System.out.println("執行 unpark(thread) 結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出結果:
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java ...
執行緒 Thread-0開始執行 park
開始執行 unpark(thread)
執行緒 Thread-0執行 park 結束
執行緒 Thread-0開始執行 park 第二次
執行緒 Thread-0執行 park 第二次 結束
執行 unpark(thread) 結束
- 可以看出執行緒中斷,park 會繼續執行,並且沒有丟擲異常。
- thread.interrupt(); 呼叫之後, 設定執行緒中斷標示,unpark 沒有清除中斷標示,第二個 park 也會繼續執行。
使用診斷工具
liuzhihang % > jps
76690 LockSupportTest
77130 Jps
liuzhihang % > jstack 77265
...
"main" #1 prio=5 os_prio=31 tid=0x00007f7f3e80a000 nid=0xe03 waiting on condition [0x000070000dfcd000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at com.liuzhihang.source.LockSupportTest.main(LockSupportTest.java:14)
- 中間省略部分,但是可以看出執行緒進入
WAITING
狀態
原始碼分析
public class LockSupport {
private static final sun.misc.Unsafe UNSAFE;
/**
* 為執行緒 thread 設定一個許可
* 無許可,則新增一個許可,有許可,則不新增
* 如果執行緒因為 park 被阻塞, 新增許可之後,會解除阻塞狀態
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/**
* 有許可,則使用該許可
* 沒有許可,阻塞執行緒,直到獲得許可
* 傳遞 blocker 是為了方便使用診斷工具
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
/**
* 設定執行緒的 blocker 屬性
*/
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
}
LockSupport 的 park unpark 方法,實際呼叫的是底層 Unsafe 類的 native 方法。
public final class Unsafe {
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
}
既然呼叫了 Unsafe 到此處肯定不能善罷甘休。
hotspot 原始碼
這塊是下載的官方包中的原始碼,閱讀並查閱資料瞭解的大概邏輯,不清楚之處,希望指匯出來。
也可以直接跳過直接看結論。
檢視jdk原始碼
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/share/vm/runtime/park.hpp
這塊在以 os_linux 程式碼為例
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/os/linux/vm/os_linux.cpp
- 在底層維護了一個
_counter
通過更新_counter
的值來標示是否有證明。 - 在 park 時,判斷
_counter
為 0,則阻塞等待,為 1 則獲得更新為 0 並返回。 - 在 unpark 時,判斷
_counter
為 0,則給予憑證,並喚醒執行緒,為 1 則直接返回。
總結
總結也是和預想的是相同的。
- 許可(permit)的上限是1,也就是說只有 0 或 1 。
- park: 沒有許可的時候,permit 為 0 ,呼叫 park 會阻塞;有許可的時候,permit 為 1 , 呼叫 park 會扣除一個許可,然後返回。
- unpark:沒有許可的時候,permit 為 0 ,呼叫 unpark 會增加一個許可,因為許可上限是 1 , 所以呼叫多次也只會為 1 個。
- 執行緒初始的時候是沒有許可的。
- park 的當前執行緒如果被中斷,會立即返回,並不會丟擲中斷異常。
擴充套件
- park/unpark 和 wait/notify 區別
- park 阻塞當前執行緒,unpark 喚醒指定執行緒。
- wait() 需要結合鎖使用,並釋放鎖資源,如果沒有設定超時時間,則需要 notify() 喚醒。而 notify() 是隨機喚醒執行緒。