??因為本文的內容大部分是以生產者/消費者模式來進行講解和舉例的,所以在開始學習本文介紹的幾種執行緒間的通信方式之前,我們先來熟悉一下生產者/消費者模式,
??在實際的軟體開發程序中,經常會碰到如下場景:某個模塊負責產生資料(可能是訊息、檔案、任務等),這些資料由另一個模塊來負責處理,產生資料的模塊,就形象地被稱為生產者;而處理資料的模塊,就被稱為消費者,
??單單抽象出生產者和消費者,還稱不上是生產者/消費者模式,該模式還需要有一個緩沖區處于生產者和消費者之間來作為一個中介,生產者把資料放入緩沖區,而消費者從緩沖區取出資料,因此,生產者/消費者模式大概的結構如下圖:

??我們可以使用不同的執行緒來模擬生產者和消費者,對應的,生產資料的執行緒就被稱為是生產者執行緒,而消費資料的執行緒就被稱為是消費者,而緩沖區則可以選用那些執行緒安全的資料結構來模擬,因此,緩沖區在這里就起到了執行緒間通信工具的作用,本文將會通過生產者/消費者模式作為例子來介紹幾種執行緒間的通信方式,
一.阻塞佇列
1.BlockingQueue介面
??如何選擇合適的資料結構來作為緩沖區呢?首先我們來分析一下我們的需求,首先,消費者應該是要按照生產者生產的順序來消費資料的,那么我們腦海中浮現的一定是具有先進先出特性的佇列了,其次,既然是在多執行緒之間進行傳遞,那么這個類一定是執行緒安全的,因此,緩沖區應該使用執行緒安全的佇列,我們首先想到的應該是ConcurrentLinkedQueue,它是使用鏈表實作的執行緒安全的佇列,但是,這個類是非阻塞的,這意味著當生產者向緩沖區中放入資料時,緩沖區是否已滿時需要生產者自己去判斷的;同理,當消費者去消費緩沖區中的資料時,緩沖區是否為空也是需要自己去判斷的,由于這個類是非阻塞的,因此我們只能在執行緒中不斷的去輪詢緩沖區,這顯然不是多執行緒編程該有的實作方式,
??那么,有沒有可以阻塞執行緒的佇列呢?答案是肯定的,java.util.concurrent包中的BlockingQueue介面定義了阻塞佇列的行為,當阻塞佇列中沒有資料的時候,消費者端的執行緒會被掛起直到有資料被放入佇列;當阻塞佇列中填滿資料的時候,生產者端的執行緒會被掛起直到佇列中有空的位置,
??下面來介紹BlockingQueue介面中定義的方法,BlockingQueue介面時Queue介面的子介面,因此它繼承了Queue介面中所有的方法,由于這些方法都比較簡單,因此不再贅述,這里只介紹BlockingQueue介面中新增的方法,
(1)放入資料
- void put?(E e) throws InterruptedException
將指定元素e放入佇列,如果佇列沒有空間,則當前執行緒會阻塞直到佇列有空間, - boolean offer?(E e, long timeout, TimeUnit unit) throws InterruptedException
將指定元素e放入佇列,如果佇列沒有空間,則當前執行緒會阻塞直到佇列有空間或等待超時,
(2)取出資料
- E take() throws InterruptedException
從佇列中取出元素,如果佇列中沒有元素,則當前執行緒會阻塞直到佇列中有元素, - E poll?(long timeout, TimeUnit unit) throws InterruptedException
從佇列中取出元素,如果佇列中沒有元素,則當前執行緒會阻塞直到佇列中有元素或等待超時, - int drainTo?(Collection<? super E> c)
一次性從佇列中取出所有可用的資料物件放在指定的集合中, - int drainTo?(Collection<? super E> c, int maxElements)
同上,maxElements可以限制最多獲取的元素個數,
2.BlockingQueue介面的實作類
??java.util.concurrent包為BlockingQueue介面提供了7個實作類,
(1)ArrayBlockingQueue
??ArrayBlockingQueue是基于陣列實作的有界的阻塞佇列,其內部維護了一個定長陣列來存放佇列中的資料物件,由于其是有界的,因此在構造ArrayBlockingQueue實體時,必須提供佇列的容量,這是一個非常常用的阻塞佇列,除了一個定長陣列外,ArrayBlockingQueue內部還維護了兩個整形變數,分別標識著佇列的頭部和尾部在陣列中的位置,
??阻塞佇列按照其存盤空間的容量是否受限制來劃分,可以分為有界佇列和無界佇列,有界佇列的存盤容量限制是在構造實體的時候指定的,而無界佇列實際上也有存盤容量限制,其默認的最大存盤容量為Integer.MAX_VALUE(即231-1)個元素,然而實際情況是,無界佇列往往會在還沒到達存盤容量限制時就已經造成了OutOfMemoryError,
??ArrayBlockingQueue在寫入資料和獲取資料時,使用的是同一個鎖物件,這就意味著兩者無法達到真正的并行,其實按照實作原理來分析,ArrayBlockingQueue完全在兩種操作上使用不同的鎖,從而實作生產者和消費者操作的完全并行,Doug Lea之所以沒這樣去做,也許是因為ArrayBlockingQueue的資料寫入和獲取操作已經足夠輕巧,以至于引入獨立的鎖機制,除了給代碼帶來額外的復雜性外,在性能上并不會有太大的提升,
??此外,在創建ArrayBlockingQueue實體時,我們還可以指定內部的鎖是否采用公平鎖,默認情況下采用非公平鎖,
(2)LinkedBlockingQueue
??LinkedBlockingQueue是基于鏈表實作的阻塞佇列,其既可以是有界的,也可以是無界的,如果在構造LinkedBlockingQueue實體時沒有提供佇列的容量,則會構造出一個無界的佇列,反之則會構造出一個有界的佇列,
??LinkedBlockingQueue也是一個非常常用的阻塞佇列,其內部維護了一個鏈表,對于資料的寫入操作是在鏈表頭部進行的,而對于資料的獲取操作是在鏈表尾部進行的,LinkedBlockingQueue內部對于資料的寫入和讀取采用了兩個鎖來控制,即putLock和takeLock,它們都是非公平鎖,兩種操作對應了兩把鎖意味著在高并發的情況下生產者和消費者可以并行地操作佇列中的資料,這可以有效地提高整個佇列的并發性能,但是,相較于ArrayBlockingQueue,LinkedBlockingQueue在寫入和讀取資料時,需要動態地創建和洗掉鏈表節點,在高并發和資料量大的時候,GC壓力很大,
??ArrayBlockingQueue和LinkedBlockingQueue是兩個最普通也是最常用的阻塞佇列,一般情況下,在處理多執行緒間的生產者消費者問題時,使用這兩個類足以解決大部分問題,
(3)DelayQueue
??DelayQueue是一個存放延時元素的無界阻塞佇列,它對佇列中的元素做出了限制,即E extends Delayed,這意味著佇列中的元素必須實作Delayed介面,Delayed介面用于標記具有延時功能的物件,即只有在給定的延遲時間結束之后才能對該物件進行操作,Delayed介面中只定義了一個方法getDelay?(TimeUnit unit),但是由于它是Comparable介面的子介面,因此它還繼承了compareTo方法,這個方法在實作時需要根據getDelay的結果來進行排序,
??DelayQueue內部維護了一個優先級佇列,即PriorityQueue,該佇列是以延時結束的時間做為優先級來存放元素的,延時結束時間越早,優先級越高,當試圖從延時佇列中取出元素時,會先從優先級佇列中取出優先級最高的元素,若該元素延時時間已經結束,則直接回傳;否則將會阻塞當前執行緒直到延時結束,向佇列中放入元素時,除了獲取操作優先級佇列的鎖之外沒有其他限制,該佇列的讀取和寫入使用的是同一把鎖(非公平鎖),因此該佇列的消費者和生產者無法并行操作,
(4)PriorityBlockingQueue
??PriorityBlockingQueue很好理解,可以將其看作執行緒安全的、具有阻塞功能的無界優先級佇列,PriorityBlockingQueue的put和take操作都加了鎖,并且它們使用的是同一把非公平鎖,這意味著該佇列上的消費者和生產者無法并行操作,由于該佇列是無界的,因此該佇列不會阻塞生產者,但是當佇列中沒有元素的時候會阻塞消費者,雖然該佇列是無界的,但是它仍然提供了可以指定佇列初始化大小的構造方法,這是因為該佇列會在佇列已滿的情況下進行自動擴容,
(5)SynchronousQueue
??SynchronousQueue是一種較為特殊的阻塞佇列,其內部并沒有存盤佇列元素的空間,當生產者執行緒執行put操作時,如果沒有消費者執行緒在執行take操作,那么該生產者執行緒會被阻塞;當消費者執行緒在執行take操作時,如果沒有生產者執行緒在執行put操作,那么該消費者執行緒也會被阻塞,
??SynchronousQueue類提供了兩個構造方法,分別是SynchronousQueue()和SynchronousQueue(boolean fair),第一種構造器默認采用了非公平策略(實際上是LIFO),第二種構造器則可以指定佇列采用非公平策略還是公平策略,
??此外,由于SynchronousQueue本身并不存盤元素,因此該佇列對于Queue介面中定義的大部分方法都具有固定的回傳值,例如peek()總是回傳null,size()總是回傳0等,
(6)LinkedTransferQueue
??LinkedTransferQueue是一種用鏈表實作的無界阻塞佇列,它實作了TransferQueue介面,而TransferQueue介面是BlockingQueue介面的子介面,因此它也是阻塞佇列的一種,
??下面是TransferQueue介面定義的方法:

??除了這幾個方法外,該佇列其他方法的行為和LinkedBlockingQueue類似,這里不再過多贅述,
(7)LinkedBlockingDeque
??顧名思義,這個類是用鏈表實作的阻塞雙端佇列,和LinkedBlockingQueue類似,它既可以是有界的,也可以是無界的,實際上,除了具有雙端佇列的特性外,該類與LinkedBlockingQueue十分相似,可以參照上面對LinkedBlockingQueue的介紹來理解它,這里不再詳細介紹,
二.信號量Semaphore
??Semaphore類是一個計數信號量,為了便于討論,我們把代碼所訪問的特定資源或者執行特定操作的機會同意看作是一種資源,可以將其稱之為虛擬資源,Semaphore相當于虛擬資源訪問許可管理器,它可以用來控制同一時間內對虛擬資源的訪問次數,為了對虛擬資源的訪問進行流量控制,我們必須使相應代碼只有在獲得許可的情況下才能夠訪問這些資源,基于這種思想,在訪問虛擬資源前應該先申請許可,在訪問后應該釋放許可,
??Semaphore的acquire和release方法分別用于申請和釋放許可,如果當前可用的許可數等于0或小于0(在構造Semaphore實體的時候可以指定許可數為0或負數),那么acquire方法會使執行執行緒暫停,Semaphore內部維護了一個佇列來存盤這些被暫停的執行緒,默認情況下,Semaphore使用非公平策略,當然也可以在構造方法中顯式指定Semaphore實體使用公平策略還是非公平策略,
??下面是Semaphore類提供的所有方法:

三.管道流
??Java語言提供了各種各樣的輸入/輸出流,使我們能夠很方便地對資料進行操作,其中管道流是一種特殊的流,用于在不同的執行緒間直接傳送資料,一個執行緒發送資料到管道,另一個執行緒從管道中讀取資料,通過使用管道,可以實作不同執行緒間的通信,而無需借助臨時檔案等資料中介,
??和其他流類似,管道流也分為位元組流和字符流,其中位元組流對應的輸入流和輸出流分別是PipedInputStream和PipedOutputStream,字符流對應的輸入流和輸出流分別是PipedReader和PipedWriter,
??管道流實際上是使用一個回圈緩沖陣列來實作的,輸入流從這個陣列中讀資料,輸出流向這個陣列中寫資料,當緩沖區滿時,輸出流所在的執行緒將會阻塞,當緩沖區空時,輸入流所在的執行緒將會阻塞,這個陣列位于輸入流內部,默認大小為1024,也可以通過管道輸入流的構造方法來指定緩沖陣列大小,
??管道輸入流和輸出流在使用之前必須先建立連接,可以通過輸入流或輸出流的構造方法或connect方法來使兩個流建立連接,假設in是執行緒A的輸入流,out是執行緒B的輸出流,那么可以通過以下幾種方法來建立連接:
PipedInputStream in = new PipedInputStream(out); //方法1
PipedInputStream in = new PipedInputStream(); //方法2
in.connect(out);
PipedOutputStream out = new PipedOutputStream(in); //方法3
PipedOutputStream out = new PipedOutputStream(); //方法4
out.connect(in);
??不要在同一個執行緒中同時使用管道輸入流和管道輸出流,這樣有可能會引起死鎖,因為當緩沖區滿時,如果繼續向輸出流中寫入資料,則會阻塞當前執行緒,從而造成從輸入流中讀取資料的代碼永遠不會被執行到,造成死鎖;緩沖區空時,如果繼續從輸出流中讀取資料,也會阻塞當前執行緒,從而造成向輸出流中寫入資料的代碼永遠不會被執行到,造成死鎖,
??下面是使用管道字符流撰寫的一個demo:
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class PipedStreamDemo {
private static final String content = "Hello world";
public static void main(String[] args) {
try {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter(reader);
Receiver receiver = new Receiver(reader);
Sender sender = new Sender(writer);
receiver.start();
sender.start();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class Sender extends Thread {
private PipedWriter writer;
public Sender(PipedWriter writer) {
this.writer = writer;
}
@Override
public void run() {
try {
System.out.println("Send : " + content);
char[] chars = content.toCharArray();
writer.write(chars, 0, chars.length);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static class Receiver extends Thread {
private PipedReader reader;
public Receiver(PipedReader reader) {
this.reader = reader;
}
@Override
public void run() {
try {
int ch;
while ((ch = reader.read()) != -1) {
System.out.println("Received : " + (char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
??該程式輸出如下:
Send : Hello world
Received : H
Received : e
Received : l
Received : l
Received : o
Received :
Received : w
Received : o
Received : r
Received : l
Received : d
四.交換器Exchanger
??Exchanger<V>是一個用于在兩個執行緒之間交換資料的工具類,兩個執行緒可以通過同一個Exchanger實體的exchange方法來交換資料,當一個執行緒先執行exchange方法時,它會被阻塞并等待另一個執行緒的到來;當另一個執行緒也執行exchange方法時,前一個執行緒會被喚醒,兩個執行緒完成資料交換并繼續執行,
??Exchanger提供了兩個exchange方法:

??下面是一個使用Exchanger的例子:
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Buyer(exchanger).start();
new Seller(exchanger).start();
}
private static class Buyer extends Thread {
private Exchanger<String> exchanger;
Buyer(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
try {
String money = "10元";
Thread.sleep(2000);
System.out.println("買家:拿出" + money);
String good = exchanger.exchange(money);
System.out.println("買家:得到" + good);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Seller extends Thread {
private Exchanger<String> exchanger;
Seller(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
try {
String good = "10斤大白菜";
System.out.println("賣家:拿出" + good);
System.out.println("賣家:等待買家...");
String money = exchanger.exchange(good);
System.out.println("賣家:得到" + money);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
??該程式的輸出如下:
賣家:拿出10斤大白菜
賣家:等待買家...
買家:拿出10元
買家:得到10斤大白菜
賣家:得到10元
五.執行緒中斷機制
??有時候我們需要停止一個執行緒,例如一個下載執行緒,該執行緒在沒有下載成功之前不會退出,若此時用戶覺得下載速度慢,不想下載而點擊了取消按鈕,此時我們應該停止這個下載執行緒并釋放資源,但是,由于Thread.stop、Thread.suspend和Thread.resume過于暴力而被廢棄,那么我們應該如何優雅地停止一個執行緒呢?
??Java為我們提供了執行緒中斷機制,中斷機制的思想是:一個執行緒不應該由其他執行緒來強制中斷,而是應該由執行緒自己自行判斷,中斷可以看作是由一個執行緒發送給另外一個執行緒的一種指示,該指示用于表示發起執行緒希望目標執行緒停止其正在執行的操作,但是,中斷一個執行緒并不代表馬上停止該執行緒,而只是通知該執行緒應該中斷了,是否應該中斷以及如何回應中斷則應該交給該執行緒來自行處理,
??在Java的API或語言規范中,并沒有將中斷與任何取消語意關聯起來,但實際上,如果在取消之外的其他操作中使用中斷,那么都是不合適的,并且很難支持起更大的應用,
1.API
??實際上,每個執行緒內部都有一個boolean型別的中斷標記,當中斷一個執行緒時,該執行緒內部的中斷標記將會被設定為true,以下是Thread類中與中斷有關的三個方法:
void interrupt()
boolean isInterrupted()
static boolean interrupted()
??下面將分別對這三個方法進行介紹,
(1)interrupt
??呼叫一個執行緒的interrupt方法會將該執行緒的中斷標記設定為true,
- 如果該執行緒由于呼叫Object.wait()、Object.wait(long)、Object.wait(long, int)、Thread.join()、Thread.join(long)、Thread.join(long, int)、Thread.sleep()或Thread.sleep(long, int)而進入等待狀態,該執行緒的中斷標記將會被清除并收到一個InterruptedException,
- 如果該執行緒阻塞在一個基于InterruptibleChannel的I/O操作上,這個channel將會被關閉并收到一個ClosedByInterruptException,
- 如果該執行緒被阻塞在一個Selector里,則該執行緒會馬上從選擇操作中回傳,回傳值可能是非0值,就好像Selector的wakeup方法被呼叫過一樣,
- 如果以上條件均不成立,那么該執行緒僅僅只是中斷標記被設定為true,并不會表現出其他行為,
??上面的2、3兩個條件與NIO有關,這里只是順便提到而已,后續會推出關于NIO的系列教程,
(2)isInterrupted
??該方法較為簡單,只是回傳該執行緒的中斷標記,并不會影響該中斷標記和產生其他行為,
(3)interrupted
??該方法是一個靜態方法,也會回傳該執行緒的中斷標記,但是該方法與isInterrupted最大的區別在于該方法會清除執行緒的中斷狀態,例如,如果當前執行緒已經被中斷,那么呼叫interrupted方法將會回傳true并同時清除中斷狀態;如果當前執行緒未被中斷,則會回傳false,這個方法的好處在于回傳了執行緒中斷標記的同時還清除了中斷標記,
2.執行緒在不同狀態下對中斷的反應
??執行緒一共有6種狀態,分別是NEW、RUNNABLE、WAITING、TIMED_WAITING、BLOCKED和TERMINATED,在不同狀態下,執行緒可能會對中斷產生不同的反應,
(1)NEW/TERMINATED
??由于處于NEW狀態的執行緒還沒有啟動,而處于TERMINATED狀態的執行緒已經終止,Java認為對處于這兩種狀態下的執行緒進行中斷毫無意義,所以并不會將執行緒的中斷標識設定為true,也不會產生其他的行為,
public class NewAndTerminatedDemo {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println(thread.getState());
thread.interrupt();
System.out.println(thread.isInterrupted());
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getState());
thread.interrupt();
System.out.println(thread.isInterrupted());
}
}
??上面的例子輸出如下:
NEW
false
TERMINATED
false
(2)RUNNABLE
??處于RUNNABLE狀態下的執行緒被中斷后,除了中斷標記被設定為true外不會產生其他行為,下面我們來做個實驗:
public class RunnableDemo {
public static void main(String[] args) {
TimeWasteThread timeWasteThread = new TimeWasteThread();
timeWasteThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timeWasteThread.interrupt();
System.out.println("The interrupt flag of timeWasteThread is " + timeWasteThread.isInterrupted());
}
private static class TimeWasteThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("The 40th fibonacci number is " + fibonacci(40));
}
}
private int fibonacci(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
}
??在上面的例子中,TimeWasteThread內部執行了一個非常耗時的操作——計算第40個斐波那契數(這種寫法在每次計算時都需要重新遞回,因此非常耗時),這樣做的目的是使它一直處于RUNNABLE狀態,我們既不希望它太快結束,也不希望使用sleep方法,因為這是下一小節要討論的內容,主執行緒在啟動TimeWasteThread兩秒后中斷了該執行緒,該程式的輸出如下:
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The interrupt flag of timeWasteThread is true
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
...
??可以看到,在主執行緒對TimeWasteThread發出了中斷信號后,TimeWasteThread的中斷標記確實變成了true,可以程式仍然在繼續執行,沒有受到任何影響,雖然主執行緒已經呼叫了TimeWasteThread的interrupt方法,可該執行緒并沒有中斷運行,既然如此,那我要這中斷機制有何用?
??我們在上面提到過,中斷機制的思想是一個執行緒是否中斷應該由該執行緒來判斷,而不應該由其他執行緒來控制,因此,我們可以在執行緒內部來判斷當前執行緒是否需要被中斷,然后做出相應的決策,
??基于這種思想,我們將TimeWasteThread的run方法修改如下:
@Override
public void run() {
while (!Thread.interrupted()) {
System.out.println("The 40th fibonacci number is " + fibonacci(40));
}
}
??重新運行該程式,輸出如下:
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The 40th fibonacci number is 102334155
The interrupt flag of timeWasteThread is true
The 40th fibonacci number is 102334155
??可以看到,TimeWasteThread在收到中斷信號后很快就停了下來,達到了中斷的目的,
(3)WAITING/TIMED_WAITING
??這兩種狀態本質上可以看作是等待狀態,只不過一個是無限期等待,而另一個是有時間限制的等待,因此放在一起討論,
??下面的例子中,我們讓SleepingThread進入TIMED_WAITING狀態后將其中斷:
public class WaitingDemo {
public static void main(String[] args) {
Thread sleepingThread = new SleepingThread();
sleepingThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepingThread.interrupt();
}
private static class SleepingThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("Thread has been interrupted.");
System.out.println("The interrupt flag of sleepingThread is " + Thread.currentThread().isInterrupted());
}
}
}
}
??該程式的輸出如下:
Thread has been interrupted.
The interrupt flag of sleepingThread is false
??在上面的例子中,主執行緒在SleepingThread處于TIMED_WAITING狀態時將其中斷,此時SleepingThread被喚醒并拋出了一個InterruptedException例外,也就是說,處于WAITING或者TIMED_WAITING狀態的執行緒被中斷時往往是通過InterruptedException例外(有時也會通過其他例外,例如ClosedByInterruptException例外)來進行通知的,
??不過,當SleepingThread由于中斷而被喚醒時,它的中斷標記卻是false,而我們確確實實在主執行緒中已經呼叫了它的interrupt方法,這是為什么呢?實際上,按照慣例,拋出InterruptedException例外的方法,通常會在拋出該例外時將當前執行緒的中斷標記重置為false,這是因為,當捕獲到InterruptedException例外時,我們已經知道執行緒被中斷了,那么此時的中斷標記對于我們來說已經沒用了,但是我們還需要手動將它再設定為false,方便下次使用,因此,為了使用方便,方法在拋出InterruptedException例外之前應該將當前執行緒的終端標記重置為false,
(4)BLOCKED
??只有在等待一個物件的監視器的執行緒才會處于BLOCKED狀態,下面我們通過一個例子來演示對處于BLOCKED狀態的執行緒呼叫interrupt方法會發生什么,
public class BlockedDemo {
private static final Integer foo = 1;
public static void main(String[] args) {
Thread thread1 = new Thread1();
thread1.start();
Thread thread2 = new Thread2();
thread2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("The state of thread2 is " + thread2.getState());
thread2.interrupt();
System.out.println("The interrupt flag of thread2 is " + thread2.isInterrupted());
System.out.println("The state of thread2 is " + thread2.getState());
}
private static class Thread1 extends Thread {
@Override
public void run() {
synchronized (foo) {
for (int i = 0; i < 5; i++) {
fibonacci(40);
}
}
}
private int fibonacci(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
private static class Thread2 extends Thread {
@Override
public void run() {
System.out.println("Thread2 tries to get the monitor of object foo.");
synchronized (foo) {
System.out.println("Thread2 got the monitor of object foo.");
System.out.println("The interrupt flag of thread2 is " + Thread.currentThread().isInterrupted());
}
}
}
}
??該程式的輸出如下:
Thread2 tries to get the monitor of object foo (1)
The state of thread2 is BLOCKED (2)
The interrupt flag of thread2 is true (3)
The state of thread2 is BLOCKED (4)
Thread2 got the monitor of object foo (5)
The interrupt flag of thread2 is true (6)
??下面依次分析每一條輸出:
??(1)Thread2啟動,嘗試獲取foo物件的監視器;
??(2)由于Thread1先獲取到了foo物件的監視器且持有較長時間,Thread2需要等待Thread1釋放foo物件的監視器,因此Thread2進入BLOCKED狀態;
??(3)對處于BLOCKED狀態的Thread2執行interrupt方法,該執行緒的中斷標記變成true;
??(4)對Thread2執行interrupt方法后,該執行緒仍然處于BLOCKED狀態;
??(5)因為Thread1釋放了foo物件的監視器,所以Thread2獲取到了該監視器,繼續執行下面的代碼;
??(6)Thread2重新進入RUNNABLE狀態后,中斷標記仍然為true,此時可以根據中斷標記來決定之后的邏輯,
??綜上,對處于BLOCKED狀態的執行緒呼叫interrupt方法,僅僅只是將該執行緒的中斷標記設定為true,除此之外沒有任何變化,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/84934.html
標籤:Java
