日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java——聊聊JUC中的线程中断机制 LockSupport

發布時間:2023/12/16 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java——聊聊JUC中的线程中断机制 LockSupport 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄:

1.什么是中斷機制?

2.如何停止中斷運行中的線程?

2.1?通過一個volatile變量實現

2.2?通過AtomicBoolean原子布爾類

2.3?通過Thread類自帶的中斷API方法實現

3.Thread類的三大API說明

3.1?實例方法interrupt(),沒有返回值

3.2?實例方法isInterrupted(),返回布爾值

3.3 當前線程的中斷標識為true,是不是線程就立刻停止?

3.4 在3.3中斷程序的基礎上,添加sleep睡眠

3.5 靜態方法public static?boolean?interrupted()

4.LockSupport

4.1 線程的等待喚醒機制

4.2 wait、notify

4.3 await、signal

4.4 park、unpark


1.什么是中斷機制?

  • 首先,一個線程不應該由其他線程來強制中斷或停止,而是應該由線程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已經被廢棄了。
  • 其次,在Java中沒有辦法立即停止一條線程,然而停止線程卻顯得尤為重要,如取消一個耗時操作。

? ? ? ? ? ? ? ? ? 因此,Java提供了一種用于停止線程的協商機制——中斷。

? ? ? ? ? ? ? ? ? 中斷只是一種協作協商機制,Java沒有給中斷增加任何語法,中斷的過程完全需要程序員自己實現。

? ? ? ? ? ? ? ? ? 若要中斷一個線程,你需要手動調用該線程的interrupt方法,該方法也僅僅是將線程對象的中斷標識設成true;

? ? ? ? ? ? ? ? ? 接著你需要自己寫代碼不斷地檢測當前線程的標識位,如果為true,表示別的線程要求這條線程中斷,此時究竟該做什么需要你自己寫代碼實現。

? ? ? ? ? ? ? ? ? 每個線程對象中都有一個標識,用于表示線程是否被中斷;該標識位為true表示中斷,為false表示未中斷;

? ? ? ? ? ? ? ? ? 通過調用線程對象的interrupt方法將該線程的標識位設為true;可以在別的線程中調用,也可以在自己的線程中調用。

尚硅谷周陽老師的例子:顧客在無煙餐廳中吸煙,服務員希望他別吸煙了,不是強行停止他吸煙,而是給他的標志位打為true,具體的停止吸煙還是要顧客自己停止。(體現了協商機制)

  • 中斷相關的三大API方法如下圖:↓↓↓


2.如何停止中斷運行中的線程?

2.1?通過一個volatile變量實現

package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit;public class InterruptDemo1 {static volatile boolean isStop = false;public static void main(String[] args) {new Thread(() -> {while (true) {if (isStop) {System.out.println(Thread.currentThread().getName() + " isStop被修改為true,線程停止");break;}System.out.println(Thread.currentThread().getName() + " hello volatile....");}}, "t1").start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {isStop = true;}, "t2").start();} }

2.2?通過AtomicBoolean原子布爾類

package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean;public class InterruptDemo2 {static AtomicBoolean atomicBoolean = new AtomicBoolean(false);public static void main(String[] args) {new Thread(() -> {while (true) {if (atomicBoolean.get()) {System.out.println(Thread.currentThread().getName() + " isStop被修改為true,線程停止");break;}System.out.println(Thread.currentThread().getName() + " hello AtomicBoolean....");}}, "t1").start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {atomicBoolean.set(true);}, "t2").start();} }

2.3?通過Thread類自帶的中斷API方法實現

package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit;public class InterruptDemo3 {public static void main(String[] args) {Thread t1 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + " isStop被修改為true,線程停止");break;}System.out.println(Thread.currentThread().getName() + " hello isInterrupted....");}}, "t1");t1.start();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {t1.interrupt();}, "t2").start();} }


3.Thread類的三大API說明

3.1?實例方法interrupt(),沒有返回值

這個interrupt()實例方法,底層實際上調用了interrupt0()這個方法,根據后面的注釋可以看到,僅僅是設置中斷標識位,而interrupt0這個方法是一個native方法,底層又調用了C。

而在jdk官方文檔中可以看到有關這個方法的敘述。

3.2?實例方法isInterrupted(),返回布爾值

這個實例方法的底層調用了一個native方法,傳入了一個布爾值,而這個值就是 是否清除中斷標識位,false表示不清除,true表示清除(即將線程的中斷標識位清除重新設置為false)。

具體來說,當對一個線程,調用 interrupt() 時:

① 如果線程處于正常活動狀態,那么會將該線程的中斷標志設置為 true,僅此而已。被設置中斷標志的線程將繼續正常運行,不受影響。所以, interrupt() 并不能真正的中斷線程,需要被調用的線程自己進行配合才行。

② 如果線程處于被阻塞狀態(例如處于sleep, wait, join 等狀態),在別的線程中調用當前線程對象的interrupt方法,那么線程將立即退出被阻塞狀態(中斷狀態將被清除),并拋出一個InterruptedException異常。

(中斷不活動的線程不會產生任何影響,看下面案例)

3.3 當前線程的中斷標識為true,是不是線程就立刻停止?

  • 否,僅僅設置了一個中斷狀態

  • 看看中斷是否會立即停止這個300的線程。否,雖然中斷標志位變了。但是i一直輸完300次,才最終停止。

package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit;public class InterruptDemo4 {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 300; i++) {System.out.println("--------- " + i);}System.out.println("t1調用interrupt()之后的中斷標識02---- " + Thread.currentThread().isInterrupted());}, "t1");t1.start();System.out.println("t1線程默認的中斷標識---- " + t1.isInterrupted());try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}t1.interrupt();System.out.println("t1調用interrupt()之后的中斷標識01---- " + t1.isInterrupted());} }

對上面的代碼稍作改變,如下:↓↓↓

package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit;public class InterruptDemo5 {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 300; i++) {System.out.println("--------- " + i);}System.out.println("after t1.interrupt()---第2次---- " + Thread.currentThread().isInterrupted());}, "t1");t1.start();System.out.println("before t1.interrupt()---- " + t1.isInterrupted());try {TimeUnit.MILLISECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}t1.interrupt();System.out.println("after t1.interrupt()---第1次--- " + t1.isInterrupted());try {TimeUnit.MILLISECONDS.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("after t1.interrupt()---第3次--- " + t1.isInterrupted());} }

在輸出結果中,我們可以看到和我們預想的都一樣,只有最后一行輸出,t1線程它自己不是已經打斷了嗎?那中斷標識就應該是 true 啊?為什么變成了false???

原因是上面的代碼中,t1線程打印300次i,而最后一行輸出代碼是在2000ms之后的,t1線程是完全可以在這個時間內完成300次i的打印工作,所以程序運行到最后一行輸出,t1線程已經結束死亡了,再根據 interrupt 方法api中的這句話:

      • 中斷不存在的線程不需要任何效果。

我們就懂了,中斷不存在的線程沒什么意義的,所以這里的中斷標識自然就恢復成了默認值 false。

3.4 在3.3中斷程序的基礎上,添加sleep睡眠

package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit;public class InterruptDemo6 {public static void main(String[] args) {Thread t1 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + " 中斷標識位:" +Thread.currentThread().isInterrupted() + " 線程終止....");break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("---- hello InterruptDemo6");}}, "t1");t1.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {t1.interrupt();}, "t2").start();} }

這個程序是停不下來的,我是不想耗費太多CPU資源,手動停止了。?

原因就是:

      • 如果該線程阻塞的調用wait() , wait(long) ,或wait(long, int)的方法Object類,或者在join() , join(long) , join(long, int) , sleep(long) ,或sleep(long, int) ,這個類的方法,那么它的中斷狀態將被清除,并且將收到一個InterruptedException 。

      • 所以這個時候線程t1的中斷標識位被清除,恢復成了false,那么久永遠也停不下來了。

如何修改上面的代碼,使得程序正常運行停止呢?? ?→?

  • 中斷標志位 默認是false。
  • t2 ----->t1發出了中斷協商,t2調用t1.interrupt(),中斷標志位true。
  • 中斷標志位true,正常情況下,程序停止。
  • 中斷標志位true,異常情況下,InterruptedException,將會把中斷狀態清除,并且將收到InterruptedException。中斷標志位false導致無限循環。
  • 在catch塊中,需要再次給中斷標志位設置為true,2次調用停止。
  • package com.szh.demo.interrupt;import java.util.concurrent.TimeUnit;public class InterruptDemo6 {public static void main(String[] args) {Thread t1 = new Thread(() -> {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + " 中斷標識位:" +Thread.currentThread().isInterrupted() + " 線程終止....");break;}try {Thread.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt(); //關鍵代碼e.printStackTrace();}System.out.println("---- hello InterruptDemo6");}}, "t1");t1.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {t1.interrupt();}, "t2").start();} }

    3.5 靜態方法public static?boolean?interrupted()

    靜態方法,Thread.interrupted();判斷線程是否被中斷,并清除當前中斷狀態這個方法做了兩件事:1 返回當前線程的中斷狀態? ? 2 將當前線程的中斷狀態設為false(這個方法有點不好理解,因為連續調用兩次的結果可能不一樣。)

    package com.szh.demo.interrupt;public class InterruptDemo7 {public static void main(String[] args) {System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());System.out.println("-----1");Thread.currentThread().interrupt();//中斷標志位設置為trueSystem.out.println("-----2");System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());} }

    前兩次調用沒啥說的,因為main主線程并沒有中斷,第三次調用的時候,因為上面已經 interrupt 了,所以被中斷了,這里中斷標識位肯定就是 true。此時這個靜態方法在中斷之后第一次調用(返回當前線程的中斷狀態,被中斷了就是true;第二件事,將當前線程的中斷標識重置為false)。所以當最后一行再次調用它的時候,就是false了。?

    看一下這個靜態方法的源碼:↓↓↓? ? 在那個isInterrupt實例方法中傳入的 一個布爾值,而這個值就是 是否清除中斷標識位,false表示不清除,true表示清除(即將線程的中斷標識位清除重新設置為false)。

    這兩個方法在底層都調用了native方法isInterrupted。? 只不過傳入參數ClearInterrupted一個傳參傳了true,一個傳了false。

    靜態方法interrupted() 中true表示清空當前中斷狀態。? 實例方法isInterrupted 則不會。


    4.LockSupport

    用于創建鎖和其他同步類的基本線程阻塞原語。

    這個類與每個使用它的線程相關聯,一個許可證(在Semaphore類的意義上)。 如果許可證可用,則呼叫park將park返回,在此過程中消耗它; 否則可能會阻止。 致電unpark使許可證可用,如果尚不可用。 (與信號量不同,許可證不能累積,最多只有一個。)

    核心就是park()和unpark()方法

    • park()方法是阻塞線程

    • unpark()方法是解除阻塞線程

    4.1 線程的等待喚醒機制

  • 使用Object中的wait()方法讓線程等待,使用Object中的notify()方法喚醒線程。(有局限性)

  • 使用JUC包中Condition的await()方法讓線程等待,使用signal()方法喚醒線程。(有局限性)

  • LockSupport類可以阻塞當前線程以及喚醒指定被阻塞的線程。

  • 4.2 wait、notify

    package com.szh.demo.locksupport;import java.util.concurrent.TimeUnit;public class LockSupportDemo1 {public static void main(String[] args) {final Object obj = new Object();new Thread(() -> {synchronized (obj) {System.out.println(Thread.currentThread().getName() + " --- come in");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + " --- 被喚醒了");}, "t1").start();try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {synchronized (obj) {obj.notify();System.out.println(Thread.currentThread().getName() + " --- 發出通知");}}, "t2").start();} }

    異常情況1:將 synchronized 同步代碼塊對應的代碼注釋掉。?

    package com.szh.demo.locksupport;import java.util.concurrent.TimeUnit;public class LockSupportDemo1 {public static void main(String[] args) {final Object obj = new Object();new Thread(() -> {//synchronized (obj) {System.out.println(Thread.currentThread().getName() + " --- come in");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}//}System.out.println(Thread.currentThread().getName() + " --- 被喚醒了");}, "t1").start();try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {//synchronized (obj) {obj.notify();System.out.println(Thread.currentThread().getName() + " --- 發出通知");//}}, "t2").start();} }

    異常情況2:將wait和notify順序調換。?

    package com.szh.demo.locksupport;import java.util.concurrent.TimeUnit;public class LockSupportDemo1 {public static void main(String[] args) {final Object obj = new Object();new Thread(() -> {try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj) {System.out.println(Thread.currentThread().getName() + " --- come in");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + " --- 被喚醒了");}, "t1").start();// try { // TimeUnit.SECONDS.sleep(3L); // } catch (InterruptedException e) { // e.printStackTrace(); // }new Thread(() -> {synchronized (obj) {obj.notify();System.out.println(Thread.currentThread().getName() + " --- 發出通知");}}, "t2").start();} }

    小總結

    • 線程先要獲得并持有鎖,必須在鎖塊(synchronized或lock)中

    • 必須要先等待后喚醒,線程才能夠被喚醒。要保證先wait,后notify才OK。

    • wait和notify方法必須要在同步塊或者方法里面,且成對出現使用。

    4.3 await、signal

    package com.szh.demo.locksupport;import javax.swing.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class LockSupportDemo2 {private static Lock lock = new ReentrantLock();private static Condition condition = lock.newCondition();public static void main(String[] args) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + " --- come in");condition.await();System.out.println(Thread.currentThread().getName() + " --- 被喚醒了");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}, "t1").start();try {TimeUnit.SECONDS.sleep(1L);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {lock.lock();try {condition.signal();System.out.println(Thread.currentThread().getName() + " --- 發出通知");} finally {lock.unlock();}}, "t2").start();} }

    異常情況1:將對應的加鎖解鎖的代碼注釋掉,報錯信息和第一個案例是一樣的。?

    異常情況2:先進行 signal,再進行 await,報錯信息和第一個案例是一樣的。?

    小總結

    • 線程先要獲得并持有鎖,必須在鎖塊(synchronized或lock)中

    • 必須要先等待后喚醒,線程才能夠被喚醒。一定要先await后signal,不能反了

    • Condition中的線程等待和喚醒方法,需要先獲取鎖

    4.4 park、unpark

    調用LockSupport.park()時,發現它調用了unsafe類,并且默認傳了一個0。

    permit默認是零,所以一開始調用park()方法,當前線程就會阻塞,直到別的線程將當前線程的permit設置為1時,park方法會被喚醒,然后會將permit再次設置為零并返回。


    調用LockSupport.unpark();時,也調用了unsafe類。

    調用unpark(thread)方法后,就會將thread線程的許可permit設置成1(注意多次調用unpark方法,不會累加,permit值還是1)會自動喚醒thread線程,即之前阻塞中的LockSupport.park()方法會立即返回。

    解決上面兩個案例的第一個問題:必須放在鎖塊中,LockSupport不需要這樣做。?

    package com.szh.demo.locksupport;import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport;public class LockSupportDemo3 {public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + " --- come in");LockSupport.park();System.out.println(Thread.currentThread().getName() + " --- 被喚醒了");}, "t1");t1.start();try {TimeUnit.SECONDS.sleep(2L);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + " --- 發出通知");}, "t2").start();} }

    解決上面兩個案例的第一個問題:必須先等待,后喚醒,LockSupport不需要這樣做,先喚醒后等待照樣OK。?

    package com.szh.demo.locksupport;import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport;public class LockSupportDemo3 {public static void main(String[] args) {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " --- come in " + System.currentTimeMillis());LockSupport.park();System.out.println(Thread.currentThread().getName() + " --- 被喚醒了 " + System.currentTimeMillis());}, "t1");t1.start();new Thread(() -> {LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + " --- 發出通知");}, "t2").start();} }

    這里會先執行t2線程的unpark方法,此時t1線程手中就有了一張許可證,當t1線程睡眠3秒之后,執行代碼,走到park方法不會再阻塞,直接拿出許可證,繼續向下執行,所以看代碼的花費時間就知道,這里的park是無效沒有阻塞的。?

    jdk官方文檔中說了,與信號量不同,許可證不能累積,最多只有一個。

    老子就不信這個邪,我非得給你來兩個許可證,看看下面的代碼。

    package com.szh.demo.locksupport;import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport;public class LockSupportDemo3 {public static void main(String[] args) {Thread t1 = new Thread(() -> {try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " --- come in " + System.currentTimeMillis());LockSupport.park();LockSupport.park();System.out.println(Thread.currentThread().getName() + " --- 被喚醒了 " + System.currentTimeMillis());}, "t1");t1.start();new Thread(() -> {LockSupport.unpark(t1);LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + " --- 發出通知");}, "t2").start();} }

    可以看到,代碼卡在這里了,這是因為你雖然發了兩個許可證,但是最多只能持有一個,那么當第二次park嘗試再去獲取許可證時,已經不可能了,因為t1線程手中的那個許可證已經被第一次park的時候消費掉了。

    當調用park方法時如果有憑證,則會直接消耗掉這個憑證然后正常退出;如果無憑證,就必須阻塞等待憑證可用。

    而unpark則相反, 它會增加一個憑證, 但憑證最多只能有1個, 累加無效。?


    針對park和unpark方法的代碼實測結論:

  • park:unpark = 1:1,代碼正常執行無誤。
  • park:unpark = 1:n,代碼正常執行無誤。(盡管unpark了多次,但是當前線程最多只能持有1個許可證,之后也只park了一次,消費了一個許可證,所以沒問題,但還是不推薦這樣寫)
  • park:unpark = n:1,代碼卡死無法結束。(當前線程最多只能持有1個許可證,park一次消費一個,park多次直接無證,當前線程無法正常結束)
  • park:unpark = n:n,代碼卡死無法結束。(原因在上面說過了)
  • 總結

    以上是生活随笔為你收集整理的Java——聊聊JUC中的线程中断机制 LockSupport的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。