LockSupport.park()分析


本文一些理解和代碼參考了看過的網上一些文章,感謝原作者們

之前在https://www.cnblogs.com/lyhero11/p/13681548.html 中討論了java並發編程里的“等待-通知”范式,里邊提到了LockSupport,最近也在研究之前的一份tomcat線程dump的樣本和AQS、也都涉及到這個類,所以這里有必要再深入一下。

LockSupport工具類

LockSupport打開源碼一看都是static方法,典型的工具類模式,是一個在jdk里邊相對偏底層的工具類,是用來編寫其他稍微上層一些的並發庫的工具。
作用是用來對Thread進行WAITING(TIMED_WAITING)和RUNNABLE之間的狀態轉換。對比一下Object.wait和notify先要synchronized鎖住object -> wait阻塞等待 -> 釋放鎖 -> notify -> 搶鎖獲得鎖 -> 從wait往下執行 這樣的一個流程,LockSupport不需要先獲取對象的鎖,可以直接LockSupport.park()阻塞當前線程,LockSupport.park(object)當前線程的阻塞和object鎖沒有必然關系,僅是用來標識當前線程是阻塞在object上這樣一種邏輯關系。

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker); // UNSAFE.putObject(Thread.currentThread(), parkBlockerOffset, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

可以用如下例子來理解驗證一下:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LockSupportTest {
	private static Object lock = new Object();

	static class MyThread extends Thread {

		public MyThread(String name) {
			super(name);
		}

		@Override
		public void run() {
			//synchronized(lock) {
			//System.out.println(this.getName()+"拿到lock並開始執行run");
			log.info(this.getName() + "開始執行");
			LockSupport.park(lock);
			if (this.isInterrupted()) {
				log.info(this.getName() + "被中斷");
			}
			log.info(this.getName() + "繼續執行");
			//}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		MyThread t1 = new MyThread("t1");
		MyThread t2 = new MyThread("t2");

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		t2.start();
		TimeUnit.SECONDS.sleep(1);

		LockSupport.unpark(t2);
		TimeUnit.SECONDS.sleep(1);
		t1.interrupt();

		t1.join();
		t2.join();
	}
}

執行結果:

14:11:45.617 [t1] INFO com.lock.test.LockSupportTest - t1開始執行
14:11:46.620 [t2] INFO com.lock.test.LockSupportTest - t2開始執行
14:11:47.623 [t2] INFO com.lock.test.LockSupportTest - t2繼續執行
14:11:48.626 [t1] INFO com.lock.test.LockSupportTest - t1被中斷
14:11:48.626 [t1] INFO com.lock.test.LockSupportTest - t1繼續執行

45秒t1開始執行,然后阻塞在LockSupport.park(lock),1秒后t2開始執行,然后也是阻塞在LockSupport.park(lock),1秒后unpark(t2),t2繼續執行,再1秒后,t1.interrupt()然后t1繼續執行。這是完整的一個流程。可見park(lock)跟object鎖沒關系。

LockSupport比較偏底層,再下面就是unsafe和native方法了,這里涉及了更為底層的java對象在內存中的layout以及本地線程調度的知識,這里先不表,點到為止。。。
image

LockSupport源碼注釋分析

在已經了解了LockSupport里邊的park(), parkNanos(), unpark()這些基本方法的用法以后,其實更進一步深入可以看源代碼和上邊的注釋,比如park()方法:

public static void park() {
    UNSAFE.park(false, 0L);
}

源代碼過於簡單,看下注釋:

If the permit is available then it is consumed and the callreturns immediately;
otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of threethings happens:
•Some other thread invokes unpark with thecurrent thread as the target; or
•Some other thread interruptsthe current thread; or
•The call spuriously (that is, for no reason) returns.
This method does not report which of these caused themethod to return. Callers should re-check the conditions which causedthe thread to park in the first place. Callers may also determine,for example, the interrupt status of the thread upon return.
This method does not report which of these caused the method to return. Callers should re-check the conditions which caused the thread to park in the first place. Callers may also determine,for example, the interrupt status of the thread upon return.

如果permit是可用(permit=true),那么使用消費掉permit並返回。否則permit=false線程不被調度進入休眠,直到

  1. 被其他線程用unpark()喚醒
  2. 其他線程發送中斷信號給當前線程,thread.interrupt()
  3. 調用不合邏輯的(毫無理由的)返回
    這個方法不報告上述這些返回的原因,調用者需要重新檢查最先導致線程park的條件。調用者也可以在返回后確認線程的中斷狀態。

再看看unpark()

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

Makes available the permit for the given thread, if it was not already available.
If the thread was blocked on park then it will unblock. Otherwise, its next call to park is guaranteed not to block.
This operationis not guaranteed to have any effect at all if the giventhread has not been started.

如果當前線程的permit==false是不可用的,那么將permit變為可用,permit=true。
解除當前線程在park上的阻塞,或者如果當前線程沒阻塞在park上、那么將會保證其下一次park不會被阻塞。
如果線程還沒start,則不會得到上面的保證。

總結

綜合上面信息,我們可以知道:

  • park是響應中斷的,中斷會使得park結束,只不過不會拋中斷異常,可以在park返回時檢查中斷狀態來確認被中斷了。
  • 然后park的時候是要檢查內部的一個線程permit狀態的(對調用者透明),如果permit=true,那么park就不阻塞,直接返回,如果permit=false則阻塞。
  • unpark相當於修改thread對應的permit為true,如果此時線程剛好處於park阻塞,則馬上放行,如果沒在park則由於permit=true,該線程的下一次park不會被阻塞。這個特性使得即使unpark先於park調用也不會導致notify先於wait那樣發生死鎖。
  • permit=true用過以后就消費掉了,變成permit=false
  • LockSupport.park阻塞線程不用先拿object鎖(可以用park(object)來標識邏輯關系),它是用底層native方法去修改線程上的一個Permit狀態控制線程調度系統是否調度這個線程、來達到阻塞還是執行,所以其用法比Object.wait()/notify()更靈活方便。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM