原理
官方說是線程阻塞的基本原語,可以用來創建鎖或者其它的同步類(ReentrantLock基於該類實現)。每個線程使用LockSupport都會關聯一個 “許可證”。
某個線程調用LockSupport.park,如果對應的“許可證”可用,則此次調用立即返回,否則線程將會阻塞直到中斷髮生、超時或者許可證狀態變爲可用。LockSupport.unpark(thread)可以使線程的許可證可用,但是unpark不能累積,只能使許可證從不可用到可用,不能累加可用次數。也就是說連續調用unpark和調用一次unpark效果是一樣的。
public class LockSupport {
/**
* 給某個線程頒發許可證,喚醒線程
*/
public static void unpark(Thread thread);
/**
* 阻塞調用線程直到線程中斷或者許可證狀態變爲可用,如果許可證可用(之前調用過一次unpark)則立
* 即返回
* blocker是設置阻塞線程的對象,追蹤問題用吧
*/
public static void park(Object blocker);
/**
* 阻塞調用線程直到線程中斷、等待nanos納秒或許可證狀態變爲可用,如果許可證可用(之前調用過一
*次unpark)則立即返回
* blocker是設置阻塞線程的對象,追蹤問題用吧
*/
public static void parkNanos(Object blocker, long nanos);
/**
* 阻塞調用線程直到線程中斷、到來截止時間(毫秒)或者許可證狀態變爲可用,如果許可證可用(之前調
* 用過一次unpark)則立即返回
* blocker是設置阻塞線程的對象,追蹤問題用吧
*/
public static void parkUntil(Object blocker, long deadline);
/**
* 阻塞調用線程直到線程中斷或者許可證狀態變爲可用,如果許可證可用(之前調用過一次unpark)則立
* 即返回
*/
public static void park();
/**
* 阻塞調用線程直到線程中斷、等待nanos納秒或許可證狀態變爲可用,如果許可證可用(之前調用過一
*次unpark)則立即返回
*/
public static void parkNanos(long nanos);
/**
* 阻塞調用線程直到線程中斷、到來截止時間(毫秒)或者許可證狀態變爲可用,如果許可證可用(之前調
* 用過一次unpark)則立即返回
*/
public static void parkUntil(long deadline);
}
park底層調用的是Unsafe類的park方法
public final class Unsafe {
/**
* isAbsolute表示絕對時間時間還是相對時間
* time 時間,isAbsolute爲true就是毫秒時間戳,絕對時間;isAbsolute爲false就是納秒相對時間,多
* 少納秒以後醒來
**/
public native void park(boolean isAbsolute, long time);
}
LockSupport的效果跟Thread.sleep類似,但是它跟線程sleep有以下不同:
- sleep和park都阻塞當前線程的執行,兩者都不釋放佔有的鎖資源
- sleep需要指定時間,park可指定也可不指定;
- sleep不能被其它線程喚醒,park可以被其它線程喚醒;
- sleep需要捕獲InterruptedException異常,park不需要;
- park如果想知道喚醒的原因是否是InterruptedException,需要去判斷線程的中斷狀態Thread.interrupted()。
應用舉例
jdk的很多同步操作是基於LockSupport做的,例如FutureTask的get操作(阻塞獲取Future的結果),當任務狀態沒有完成時線程調用LockSupport.park,並將作爲FutrueTask對象的等待節點放在waiters鏈表裏面。源碼如下所示(關鍵部分筆者添加了註釋):
public class FutureTask<V> implements RunnableFuture<V> {
......
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//
//筆者注:jdk很多源碼都使用這種循環的風格,每次循環只執行一個操作。例如創建一個等待節點入
//waiters鏈,分兩次循環,第一次創建節點,第二次循環入waiters鏈。筆者認爲這樣做的好處是:
//該任務可能在“創建節點”和“入waiters鏈”中間完成,這樣第二次循環就不是入waiters鏈而是直
//接返回。其它情況也是一樣,任務的完成是異步的,隨時可能在某個語句之後完成。這樣寫能讓線
//程儘快返回。
//
for (;;) {
//
//筆者注:喚醒線程的原因如果是中斷,則要拋中斷異常 並且將當前線程從waiters鏈中移除
//
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
//
//筆者注:本次循環中如果任務狀態已完成,則直接返回
//
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
//
//筆者注:創建一個等待節點,該節點會保存當前線程對象
//
q = new WaitNode();
else if (!queued)
//
//筆者注:如果等待節點沒有入隊,則入waiters鏈
//
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
//
//筆者注:如果設置了等待時間,且等待時間未到,則parkNanos阻塞線程一定的時間
//
LockSupport.parkNanos(this, nanos);
}
else
//
//筆者注:阻塞線程
//
LockSupport.park(this);
}
}
......
}
當任務完成,執行線程會遍歷waiters鏈表,並喚起每個waiter對應的線程。源碼如下所示(關鍵部分筆者添加了註釋):
public class FutureTask<V> implements RunnableFuture<V> {
......
private void finishCompletion() {
// assert state > COMPLETING;
//
//筆者注:利用自旋搶到執行權,然後去遍歷waiters鏈表
//
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
//
//筆者注:遍歷waiters鏈表,喚醒對應等待該任務的線程
//
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
//
//筆者注:此處喚醒等待線程
//
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
......
}
Demo
LockSuport各方法場景實驗,demo代碼如下:
public class LockSupportDemo {
private static Thread threadA1 = new Thread() {
public void run() {
System.out.println("A1: before park");
LockSupport.park();
System.out.println("A1: after unpark ");
}
};
private static Thread threadA2 = new Thread() {
public void run() {
//睡一小會 保證A1先執行
try {
Thread.currentThread().sleep(50);
}catch (InterruptedException e) {
System.out.println("A2 interrupted");
}
System.out.println("A2: before unpark A1");
try {
Thread.currentThread().sleep(2000);
}catch (InterruptedException e) {
System.out.println("A2 interrupted");
}
System.out.println("A2: 2s later");
}
};
/**
* A1 call park, then main call unpark(A1)
* 期望:
* A1: before park
* A2: before unpark A1
* A2: 2s later
* A1: after park
*/
protected static void testPark1() {
threadA1.start();
threadA2.start();
try {
Thread.currentThread().sleep(5000);
}catch (InterruptedException e) {
System.out.println("main interrupted");
}
LockSupport.unpark(threadA1);
}
private static Thread threadB1 = new Thread() {
public void run() {
//睡一小會 保證main可以先執行unpark
try {
Thread.currentThread().sleep(50);
}catch (InterruptedException e) {
System.out.println("B1 interrupted");
}
System.out.println("B1: before park");
LockSupport.park();
//LockSupport.park();
System.out.println("B1: after unpark ");
}
};
private static Thread threadB2 = new Thread() {
public void run() {
//比B1多睡50ms 保證B1先執行
try {
Thread.currentThread().sleep(100);
}catch (InterruptedException e) {
System.out.println("B2 interrupted");
}
System.out.println("B2: before unpark B1");
try {
Thread.currentThread().sleep(2000);
}catch (InterruptedException e) {
System.out.println("B2 interrupted");
}
System.out.println("B2: 2s later");
}
};
/**
* main call unpark(B1), then B1 call park
* 期望:
* B1: before park
* B1: after unpark
* B2: before unpark B1
* B2: 2s later
*/
protected static void testPark2() {
threadB1.start();
//C睡50ms 保證unpark先調用
LockSupport.unpark(threadB1);
//LockSupport.unpark(threadC);
threadB2.start();
}
private static Thread threadC1 = new Thread() {
public void run() {
System.out.println("C1: before park");
LockSupport.parkNanos(3000000000l);
System.out.println("C1: after unpark 3s later");
}
};
private static Thread threadC2 = new Thread() {
public void run() {
//比C1多睡50ms 保證C1先執行
try {
Thread.currentThread().sleep(50);
}catch (InterruptedException e) {
System.out.println("C2 interrupted");
}
System.out.println("C2: before unpark C1");
try {
Thread.currentThread().sleep(2000);
}catch (InterruptedException e) {
System.out.println("C2 interrupted");
}
System.out.println("C2: 2s later");
}
};
/**
* C1 call parkNanos(3000000), then main call unpark(C1) 5s later
* 期望:
* C1: before park
* C2: before unpark C1
* C2: 2s later
* C1: after unpark 3s later
* main: 5s later
*/
protected static void testParkNanos() {
threadC1.start();
threadC2.start();
try {
Thread.currentThread().sleep(5000);
}catch (InterruptedException e) {
System.out.println("main interrupted");
}
System.out.println("main: 5s later");
LockSupport.unpark(threadC1);
}
private static Thread threadD1 = new Thread() {
public void run() {
System.out.println("D1: before park");
//3s後
Long deadLine = System.currentTimeMillis() + 3000;
//過去的一個時間 則立即park不阻塞
//Long deadLine = System.currentTimeMillis() - 1000;
LockSupport.parkUntil(deadLine);
System.out.println("D1: after unpark 3s later");
}
};
private static Thread threadD2 = new Thread() {
public void run() {
//比D1多睡50ms 保證D1先執行
try {
Thread.currentThread().sleep(50);
}catch (InterruptedException e) {
System.out.println("D2 interrupted");
}
System.out.println("D2: before unpark D1");
try {
Thread.currentThread().sleep(2000);
}catch (InterruptedException e) {
System.out.println("D2 interrupted");
}
System.out.println("D2: 2s later");
}
};
/**
* D1 call parkUtil(currentTime + 3000), then main call unpark(D1) 5s later
* 期望:
* D1: before park
* D2: before unpark D1
* D2: 2s later
* D1: after unpark 3s later
* main: 5s later
*/
protected static void testParkUtil() {
threadD1.start();
threadD2.start();
try {
Thread.currentThread().sleep(5000);
}catch (InterruptedException e) {
System.out.println("main interrupted");
}
System.out.println("main: 5s later");
LockSupport.unpark(threadD1);
}
public static void main(String[] args) {
System.out.println("**************");
LockSupportDemo.testPark1();
//睡6s保證 下面test不影響前面的test輸出
try {
Thread.currentThread().sleep(6000);
}catch (InterruptedException e) {
System.out.println("main interrupted");
}
System.out.println("**************");
LockSupportDemo.testPark2();
//睡6s保證 下面test不影響前面的test輸出
try {
Thread.currentThread().sleep(6000);
}catch (InterruptedException e) {
System.out.println("main interrupted");
}
System.out.println("**************");
LockSupportDemo.testParkNanos();
//睡6s保證 下面test不影響前面的test輸出
try {
Thread.currentThread().sleep(6000);
}catch (InterruptedException e) {
System.out.println("main interrupted");
}
System.out.println("**************");
LockSupportDemo.testParkUtil();
}
}
運行結果:
**************
A1: before park
A2: before unpark A1
A2: 2s later
A1: after unpark
**************
B1: before park
B1: after unpark
B2: before unpark B1
B2: 2s later
**************
C1: before park
C2: before unpark C1
C2: 2s later
C1: after unpark 3s later
main: 5s later
**************
D1: before park
D2: before unpark D1
D2: 2s later
D1: after unpark 3s later
main: 5s later
總結
總結一下LockSupport的使用要點:
- 線程調用park阻塞自己(類似sleep),直到中斷、超時或者其它線程調用unpark;
- 線程調用park之前,如果其它線程調用了unpark,則會立即返回;
- 線程喚醒以後,線程並不知道是什麼原因被喚醒,需要去判斷時間、線程狀態(Thread.interrupted());
- 線程start之前,unpark沒有用,線程start之後park仍然會阻塞;
- 線程調用park阻塞不會釋放線程佔用的資源(鎖);