轉載請注明原創出處,謝謝!
什么是線程?
線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
線程狀態轉換
- 新建狀態(New):新創建了一個線程對象。
- 就緒狀態(Runnable):線程對象創建后,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
- 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
- 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。
阻塞的情況分三種:
- 等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
- 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
- 其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
- 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
Java線程:創建與啟動
在java中要想實現多線程,有兩種手段,一種是繼續Thread類,另外一種是實現Runable接口。
- 擴展java.lang.Thread類。此類中有個run()方法,應該注意其用法:
public void run()
如果該線程是使用獨立的Runnable運行對象構造的,則調用該Runnable對象的run方法;否則,該方法不執行任何操作並返回。Thread的子類應該重寫該方法。 - 實現java.lang.Runnable接口。
public void run()
使用實現接口Runnable的對象創建一個線程時,啟動該線程將導致在獨立執行的線程中調用對象的run方法。方法run的常規協定是,它可能執行任何所需的操作。
特別說明:線程創建的時候,優秀的編碼建議,指定線程的名稱,在dump線程的時候這樣不會是Thread-1這種了,而是自己取的線程名稱。
啟動線程在線程的Thread對象上調用start()方法,而不是run()或者別的方法。
線程的優先級
每一個 Java 線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。
Java 線程的優先級是一個整數,其取值范圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默認情況下,每一個線程都會分配一個優先級 NORM_PRIORITY(5)。
具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平台。
java線程常用方法詳解
1. sleep()
使當前線程(即調用該方法的線程)暫停執行一段時間,讓其他線程有機會繼續執行,但它並不釋放對象鎖。也就是說如果有synchronized同步快,其他線程仍然不能訪問共享數據。注意該方法要捕捉異常。
例如有兩個線程同時執行(沒有synchronized)一個線程優先級為MAX_PRIORITY,另一個為MIN_PRIORITY,如果沒有Sleep()方法,只有高優先級的線程執行完畢后,低優先級的線程才能夠執行;但是高優先級的線程sleep(500)后,低優先級就有機會執行了。總之,sleep()可以使低優先級的線程得到執行的機會,當然也可以讓同優先級、高優先級的線程有執行的機會。
2. join()
join()方法使調用該方法的線程在此之前執行完畢,也就是等待該方法的線程執行完畢后再往下繼續執行。注意該方法也需要捕捉異常。
3. yield()
該方法與sleep()類似,只是不能由用戶指定暫停多長時間,並且yield()方法只能讓同優先級的線程有執行的機會。
4. interrupt()
- 如果當前線程沒有中斷它自己(這在任何情況下都是允許的),則該線程的 checkAccess 方法就會被調用,這可能拋出 SecurityException。如果線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程中受阻,則其中斷狀態將被清除,它還將收到一個 InterruptedException。
- 如果該線程在可中斷的通道上的 I/O 操作中受阻,則該通道將被關閉,該線程的中斷狀態將被設置並且該線程將收到一個 ClosedByInterruptException。
- 如果該線程在一個 Selector 中受阻,則該線程的中斷狀態將被設置,它將立即從選擇操作返回,並可能帶有一個非零值,就好像調用了選擇器的 wakeup 方法一樣。
- 如果以前的條件都沒有保存,則該線程的中斷狀態將被設置。中斷一個不處於活動狀態的線程不需要任何作用。
5. interrupted()
測試當前線程是否已經中斷。線程的中斷狀態 由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false(在第一次調用已清除了其中斷狀態之后,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。線程中斷被忽略,因為在中斷時不處於活動狀態的線程將由此返回 false 的方法反映出來。
一個線程在未正常結束之前, 被強制終止是很危險的事情. 因為它可能帶來完全預料不到的嚴重后果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了。
那么不能直接把一個線程搞掛掉, 但有時候又有必要讓一個線程死掉, 或者讓它結束某種等待的狀態 該怎么辦呢? 優雅的方法就是, 給那個線程一個中斷信號, 讓它自己決定該怎么辦. 比如說, 在某個子線程中為了等待一些特定條件的到來, 你調用了Thread.sleep(10000), 預期線程睡10秒之后自己醒來, 但是如果這個特定條件提前到來的話, 你怎么通知一個在睡覺的線程呢? 又比如說, 主線程通過調用子線程的join方法阻塞自己以等待子線程結束, 但是子線程運行過程中發現自己沒辦法在短時間內結束, 於是它需要想辦法告訴主線程別等我了. 這些情況下, 就需要中斷.中斷是通過調用Thread.interrupt()方法來做的. 這個方法通過修改了被調用線程的中斷狀態來告知那個線程, 說它被中斷了. 對於非阻塞中的線程, 只是改變了中斷狀態, 即Thread.isInterrupted()將返回true; 對於可取消的阻塞狀態中的線程, 比如等待在這些函數上的線程, Thread.sleep(), Object.wait(), Thread.join(), 這個線程收到中斷信號后, 會拋出InterruptedException, 同時會把中斷狀態置回為false.
//Interrupted的經典使用代碼
public void run(){
try{
....
while(!Thread.currentThread().isInterrupted()&& more work to do){
// do more work;
}
}catch(InterruptedException e){
// thread was interrupted during sleep or wait
}
finally{
// cleanup, if required
}
}
很顯然,在上面代碼中,while循環有一個決定因素就是需要不停的檢查自己的中斷狀態。當外部線程調用該線程的interrupt 時,使得中斷狀態置位即變為true。這是該線程將終止循環,不在執行循環中的do more work了。這說明: interrupt中斷的是線程的某一部分業務邏輯,前提是線程需要檢查自己的中斷狀態(isInterrupted())。但是當線程被阻塞的時候,比如被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞時。調用它的interrput()方法。可想而知,沒有占用CPU運行的線程是不可能給自己的中斷狀態置位的。這就會產生一個InterruptedException異常。
/*
* 如果線程被阻塞,它便不能核查共享變量,也就不能停止。這在許多情況下會發生,例如調用
* Object.wait()、ServerSocket.accept()和DatagramSocket.receive()時,他們都可能永
* 久的阻塞線程。即使發生超時,在超時期滿之前持續等待也是不可行和不適當的,所以,要使
* 用某種機制使得線程更早地退出被阻塞的狀態。很不幸運,不存在這樣一種機制對所有的情況
* 都適用,但是,根據情況不同卻可以使用特定的技術。使用Thread.interrupt()中斷線程正
* 如Example1中所描述的,Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法
* 實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退出阻塞的狀態。更
* 確切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那么,
* 它將接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態。因此,
* 如果線程被上述幾種方法阻塞,正確的停止線程方式是設置共享變量,並調用interrupt()(注
* 意變量應該先設置)。如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就
* 將得到異常(該線程必須事先預備好處理此狀況),接着逃離阻塞狀態。在任何一種情況中,最
* 后線程都將檢查共享變量然后再停止。下面示例描述了該技術。
* */
package Concurrency.Interrupt;
class Example3 extends Thread {
volatile boolean stop = false;
public static void main(String args[]) throws Exception {
Example3 thread = new Example3();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
/*
* 如果線程阻塞,將不會檢查此變量,調用interrupt之后,線程就可以盡早的終結被阻
* 塞狀 態,能夠檢查這一變量。
* */
thread.stop = true;
/*
* 這一方法實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退
* 出阻 塞的狀態
* */
thread.interrupt();
Thread.sleep(3000);
System.out.println("Stopping application...");
System.exit(0);
}
public void run() {
while (!stop) {
System.out.println("Thread running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// 接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態
System.out.println("Thread interrupted...");
}
}
System.out.println("Thread exiting under request...");
}
}
/*
* 把握幾個重點:stop變量、run方法中的sleep()、interrupt()、InterruptedException。串接起
* 來就是這個意思:當我們在run方法中調用sleep(或其他阻塞線程的方法)時,如果線程阻塞的
* 時間過長,比如10s,那在這10s內,線程阻塞,run方法不被執行,但是如果在這10s內,stop被
* 設置成true,表明要終止這個線程,但是,現在線程是阻塞的,它的run方法不能執行,自然也就
* 不能檢查stop,所 以線程不能終止,這個時候,我們就可以用interrupt()方法了:我們在
* thread.stop = true;語句后調用thread.interrupt()方法, 該方法將在線程阻塞時拋出一個中斷
* 信號,該信號將被catch語句捕獲到,一旦捕獲到這個信號,線程就提前終結自己的阻塞狀態,這
* 樣,它就能夠 再次運行run 方法了,然后檢查到stop = true,while循環就不會再被執行,在執
* 行了while后面的清理工作之后,run方法執行完 畢,線程終止。
* */
當代碼調用中須要拋出一個InterruptedException, 你可以選擇把中斷狀態復位, 也可以選擇向外拋出InterruptedException, 由外層的調用者來決定。
不是所有的阻塞方法收到中斷后都可以取消阻塞狀態, 輸入和輸出流類會阻塞等待 I/O 完成,但是它們不拋出 InterruptedException,而且在被中斷的情況下也不會退出阻塞狀態。
嘗試獲取一個內部鎖的操作(進入一個 synchronized 塊)是不能被中斷的。
有關線程的同步操作,在后續章節會詳細講解到。
個人公眾號

