文章目錄
原理簡單講解
首先聲明,本文不會去貼native方法的cpp實現,而是以僞代碼的形式來理解這些native方法。
- Thread對象的native實現裏有一個成員代表線程的
中斷狀態
,我們可以認爲它是一個bool型的變量。初始爲false。 - Thread對象的native實現裏有一個成員代表線程是否可以阻塞的許可
permit
,我們可以認爲它是一個int型的變量,但它的值只能爲0或1。當爲1時,再累加也會維持1。初始爲0。
調用park()與unpark()
park/unpark實現的僞代碼
下面將以僞代碼的實現來說明park/unpark的實現。
park() {
if(permit > 0) {
permit = 0;
return;
}
if(中斷狀態 == true) {
return;
}
阻塞當前線程; // 將來會從這裏被喚醒
if(permit > 0) {
permit = 0;
}
}
可見,只要permit
爲1或者中斷狀態
爲true,那麼執行park
就不能夠阻塞線程。park
只可能消耗掉permit
,但不會去消耗掉中斷狀態
。
unpark(Thread thread) {
if(permit < 1) {
permit = 1;
if(thread處於阻塞狀態)
喚醒線程thread;
}
}
unpark
一定會將permit
置爲1,如果線程阻塞,再將其喚醒。從實現可見,無論調用幾次unpark
,permit
只能爲1。
park/unpark的實驗
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.park(); //因爲此時permit爲0且中斷狀態爲false,所以阻塞
}
}
上面程序執行後,程序不會運行結束,main線程阻塞。
原因是,線程默認的permit
是0,中斷狀態
爲false,所以會阻塞當前線程;
。
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.unpark(Thread.currentThread()); //置permit爲1
LockSupport.park(); //消耗掉permit後,直接返回了
}
}
上面程序執行後,程序運行結束。
原因是LockSupport.unpark(Thread.currentThread())
執行後,會使得main線程的permit
爲1。而park
時發現這個permit
爲1時,就會消耗掉這個permit
,然後直接返回,所以main線程沒有阻塞。
public class test3 {
public static void main(String[] args) throws InterruptedException {
LockSupport.unpark(Thread.currentThread());
LockSupport.park(); //消耗掉permit後,直接返回了
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
}
}
上面程序執行後,程序不會運行結束,main線程阻塞。
原因是第二次park
時,permit
爲0了,中斷狀態爲false,所以會阻塞當前線程;
。
public class test3 {
public static void main(String[] args){
Thread main = Thread.currentThread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程開始睡覺");
try {
Thread.sleep(1000);//睡一下保證是在main線程park後,纔去unpark main線程
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"拋出了中斷異常");
}
System.out.println("子線程睡醒了,開始unpark main線程");
LockSupport.unpark(main);
}
}).start();
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
//被子線程unpark後,從上一句被喚醒,繼續執行。此時permit還是爲0,中斷狀態爲true。
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
}
}
上面程序執行後,程序不會運行結束,main線程阻塞。
這個程序同上,只是之前的版本都是先unpark
,再park
。現在保證是,main線程先park
後,再去unpark
main線程。
interrupt()與park()
interrupt()實現的僞代碼
interrupt(){
if(中斷狀態 == false) {
中斷狀態 = true;
}
unpark(this); //注意這是Thread的成員方法,所以我們可以通過this獲得Thread對象
}
interrupt()
會設置中斷狀態
爲true。注意,interrupt()
還會去調用unpark
的,所以也會把permit
置爲1的。
interrupt()實驗
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().interrupt();
LockSupport.park(); //消耗掉permit後,直接返回了
}
}
上面程序執行後,程序運行結束。因爲park
執行時permit
爲1,直接返回了。
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().interrupt();
LockSupport.park(); //消耗掉permit後,直接返回了
LockSupport.park(); //因爲中斷狀態 == true,直接返回了
LockSupport.park(); //同上
}
}
上面程序執行後,程序運行結束。馬上無論怎麼park
都無法阻塞線程了,因爲此時線程的中斷狀態
爲true,函數直接返回了。
public class test3 {
public static void main(String[] args) throws InterruptedException {
Thread main = Thread.currentThread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("馬上開始睡覺");
try {
Thread.sleep(1000);//睡一下保證是在main線程阻塞後,纔去中斷main線程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡醒了,開始中斷main線程");
main.interrupt();
}
}).start();
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
//被子線程中斷後,從上一句被喚醒,繼續執行。此時permit爲0,中斷狀態爲true。
LockSupport.park(); //因爲中斷狀態 == true,直接返回了
LockSupport.park(); //同上
}
}
上面程序執行後,程序運行結束。
這個程序同上,只是之前的版本都是先中斷,再park。現在保證是,main線程先阻塞後,再去中斷main線程。
sleep()與interrupt()
sleep()實現的僞代碼
sleep(){//這裏我忽略了參數,假設參數是大於0的即可
if(中斷狀態 == true) {
中斷狀態 = false;
throw new InterruptedException();
}
線程開始睡覺;
if(中斷狀態 == true) {
中斷狀態 = false;
throw new InterruptedException();
}
}
sleep()
會去檢測中斷狀態,如果檢測到了,那就消耗掉中斷狀態後,拋出中斷異常。但sleep()
不會去動permit
。
sleep()實驗
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
try {
Thread.sleep(1000); // 消耗掉中斷狀態後,拋出異常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面程序執行後,拋出異常,程序運行結束。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
try {
Thread.sleep(1000); // 消耗掉中斷狀態後,拋出異常
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //消耗掉permit
}
}
上面程序執行後,拋出異常,程序運行結束。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
try {
Thread.sleep(1000);//消耗掉中斷狀態
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //消耗掉permit
LockSupport.park(); //因爲此時permit爲0且中斷狀態爲false,所以阻塞
}
}
上面程序執行後,拋出異常,程序不會運行結束。
public class test3 {
public static void main(String[] args){
Thread main = Thread.currentThread();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子線程開始睡覺");
try {
Thread.sleep(3000);//睡一下保證是在main線程sleep後,纔去中斷main線程
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"拋出了中斷異常");
}
System.out.println("子線程睡醒了,開始中斷main線程");
main.interrupt();
}
}).start();
try {
System.out.println("主線程開始睡覺");
Thread.sleep(5000); //main線程開始睡覺
// 當被中斷喚醒後,會消耗掉中斷狀態。喚醒後繼續執行
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"拋出了中斷異常");
}
LockSupport.park(); //消耗掉permit後,直接返回了
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
}
}
上面程序執行後,拋出異常,程序不會運行結束。
這個程序同上,只是之前的版本都是先中斷,再sleep。現在保證是,main線程先sleep後,再去中斷main線程。
wait/join 效果同sleep
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
Object lock = new Object();
synchronized (lock) {
try {
lock.wait(); //消耗掉中斷狀態
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LockSupport.park(); //消耗掉permit
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
}
}
上面程序執行後,拋出異常,程序不會運行結束。
public class test3 {
public static void main(String[] args){
Thread.currentThread().interrupt();
Thread thread = new Thread(()->{
while (true) {}
});
thread.start();
try {
thread.join(); //消耗掉中斷狀態
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park(); //消耗掉permit
LockSupport.park(); //此時permit爲0,中斷狀態爲false,必然會阻塞
}
}
上面程序執行後,拋出異常,程序不會運行結束。通過Dump Threads後,可以發現main處於WAITING (parking)
狀態,即阻塞狀態。
總結
park
調用後一定會消耗掉permit
,無論unpark
操作先做還是後做。- 如果
中斷狀態
爲true,那麼park
無法阻塞。 unpark
會使得permit
爲1,並喚醒處於阻塞的線程。interrupt()
會使得中斷狀態
爲true,並調用unpark
。sleep() / wait() / join()
調用後一定會消耗掉中斷狀態
,無論interrupt()
操作先做還是後做。
關於這一點,“如果中斷狀態
爲true,那麼park
無法阻塞”。在AQS源碼裏的acquireQueued
裏,由於acquireQueued
是阻塞式的搶鎖,線程可能重複着 阻塞->被喚醒 的過程,所以在這個過程中,如果遇到了中斷,一定要用Thread.interrupted()
將中斷狀態
消耗掉,並將這個中斷狀態
暫時保存到一個局部變量中去。不然只要遇到中斷一次後,線程在搶鎖失敗後卻無法阻塞了。