Java執行緒詳解(4)-執行緒狀態的轉換
一、執行緒狀態
執行緒的狀態轉換是執行緒控制的基礎。執行緒狀態總的可以分為五大狀態。用一個圖來描述如下:
1、新狀態:執行緒物件已經建立,還沒有在其上呼叫start()方法。
2、可執行狀態:當執行緒有資格執行,但排程程式還沒有把它選定為執行執行緒時執行緒所處的狀態。當start()方法呼叫時,執行緒首先進入可執行狀態。線上程執行之後或者從阻塞、等待或睡眠狀態回來後,也返回到可執行狀態。
3、執行狀態:執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒時執行緒所處的狀態。這也是執行緒進入執行狀態的唯一一種方式。
4、等待/阻塞/睡眠狀態:這是執行緒有資格執行時它所處的狀態。實際上這個三狀態組合為一種,其共同點是:執行緒仍舊是活的,但是當前沒有條件執行。換句話說,它是可執行的,但是如果某件事件出現,他可能返回到可執行狀態。
5、死亡態:當執行緒的run()方法完成時就認為它死去。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒。執行緒一旦死亡,就不能復生。如果在一個死去的執行緒上呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常。
二、阻止執行緒執行
對於執行緒的阻止,考慮一下三個方面,不考慮IO阻塞的情況:
睡眠;
等待;
因為需要一個物件的鎖定而被阻塞。
1、睡眠
Thread.sleep(longmillis)和Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的執行緒休眠(暫停執行),以“減慢執行緒”。當執行緒睡眠時,它入睡在某個地方,在甦醒之前不會返回到可執行狀態。當睡眠時間到期,則返回到可執行狀態。
執行緒睡眠的原因:執行緒執行太快,或者需要強制進入下一輪,因為Java規範不保證合理的輪換。
睡眠的實現:呼叫靜態方法。
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置:為了讓其他執行緒有機會執行,可以將Thread.sleep()的呼叫放執行緒run()之內。這樣才能保證該執行緒執行過程中會睡眠。
例如,在前面的例子中,將一個耗時的操作改為睡眠,以減慢執行緒的執行。可以這麼寫:
for(int i=0;i<5;i++){
// 很耗時的操作,用來減慢執行緒的執行
//for(longk=0;k<100000000;k++);
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+":"+i);
}
執行結果:
李白:0
李白:1
屈原:0
李白:2
屈原:1
李白:3
屈原:2
李白:4
屈原:3
屈原:4
這樣,執行緒在每次執行過程中,總會睡眠3毫秒,睡眠了,其他的執行緒就有機會執行了。
注意:
1、執行緒睡眠是幫助所有執行緒獲得執行機會的最好方法。
2、執行緒睡眠到期自動甦醒,並返回到可執行狀態,不是執行狀態。sleep()中指定的時間是執行緒不會執行的最短時間。因此,sleep()方法不能保證該執行緒睡眠到期後就開始執行。
3、sleep()是靜態方法,只能控制當前正在執行的執行緒。
下面給個例子:
/**
* 一個計數器,計數到100,在每個數字之間暫停1秒,每隔10個數字輸出一個字串
*/
public class CalcThread extends Thread {
public void run(){
for(int i=0;i<100;i++){
if ((i)%10==0) {
System.out.println("--------"+i);
}
System.out.print(i);
try {
Thread.sleep(1);
System.out.print(" 執行緒睡眠1毫秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new CalcThread().start();
}
}
執行結果:
--------0
0 執行緒睡眠1毫秒!
1 執行緒睡眠1毫秒!
2 執行緒睡眠1毫秒!
3 執行緒睡眠1毫秒!
4 執行緒睡眠1毫秒!
5 執行緒睡眠1毫秒!
6 執行緒睡眠1毫秒!
7 執行緒睡眠1毫秒!
8 執行緒睡眠1毫秒!
9 執行緒睡眠1毫秒!
--------10
10 執行緒睡眠1毫秒!
11 執行緒睡眠1毫秒!
12 執行緒睡眠1毫秒!
13 執行緒睡眠1毫秒!
14 執行緒睡眠1毫秒!
15 執行緒睡眠1毫秒!
16 執行緒睡眠1毫秒!
17 執行緒睡眠1毫秒!
18 執行緒睡眠1毫秒!
19 執行緒睡眠1毫秒!
--------20
20 執行緒睡眠1毫秒!
21 執行緒睡眠1毫秒!
22 執行緒睡眠1毫秒!
23 執行緒睡眠1毫秒!
24 執行緒睡眠1毫秒!
25 執行緒睡眠1毫秒!
26 執行緒睡眠1毫秒!
27 執行緒睡眠1毫秒!
28 執行緒睡眠1毫秒!
29 執行緒睡眠1毫秒!
--------30
30 執行緒睡眠1毫秒!
31 執行緒睡眠1毫秒!
32 執行緒睡眠1毫秒!
33 執行緒睡眠1毫秒!
34 執行緒睡眠1毫秒!
35 執行緒睡眠1毫秒!
36 執行緒睡眠1毫秒!
37 執行緒睡眠1毫秒!
38 執行緒睡眠1毫秒!
39 執行緒睡眠1毫秒!
--------40
40 執行緒睡眠1毫秒!
41 執行緒睡眠1毫秒!
42 執行緒睡眠1毫秒!
43 執行緒睡眠1毫秒!
44 執行緒睡眠1毫秒!
45 執行緒睡眠1毫秒!
46 執行緒睡眠1毫秒!
47 執行緒睡眠1毫秒!
48 執行緒睡眠1毫秒!
49 執行緒睡眠1毫秒!
--------50
50 執行緒睡眠1毫秒!
51 執行緒睡眠1毫秒!
52 執行緒睡眠1毫秒!
53 執行緒睡眠1毫秒!
54 執行緒睡眠1毫秒!
55 執行緒睡眠1毫秒!
56 執行緒睡眠1毫秒!
57 執行緒睡眠1毫秒!
58 執行緒睡眠1毫秒!
59 執行緒睡眠1毫秒!
--------60
60 執行緒睡眠1毫秒!
61 執行緒睡眠1毫秒!
62 執行緒睡眠1毫秒!
63 執行緒睡眠1毫秒!
64 執行緒睡眠1毫秒!
65 執行緒睡眠1毫秒!
66 執行緒睡眠1毫秒!
67 執行緒睡眠1毫秒!
68 執行緒睡眠1毫秒!
69 執行緒睡眠1毫秒!
--------70
70 執行緒睡眠1毫秒!
71 執行緒睡眠1毫秒!
72 執行緒睡眠1毫秒!
73 執行緒睡眠1毫秒!
74 執行緒睡眠1毫秒!
75 執行緒睡眠1毫秒!
76 執行緒睡眠1毫秒!
77 執行緒睡眠1毫秒!
78 執行緒睡眠1毫秒!
79 執行緒睡眠1毫秒!
--------80
80 執行緒睡眠1毫秒!
81 執行緒睡眠1毫秒!
82 執行緒睡眠1毫秒!
83 執行緒睡眠1毫秒!
84 執行緒睡眠1毫秒!
85 執行緒睡眠1毫秒!
86 執行緒睡眠1毫秒!
87 執行緒睡眠1毫秒!
88 執行緒睡眠1毫秒!
89 執行緒睡眠1毫秒!
--------90
90 執行緒睡眠1毫秒!
91 執行緒睡眠1毫秒!
92 執行緒睡眠1毫秒!
93 執行緒睡眠1毫秒!
94 執行緒睡眠1毫秒!
95 執行緒睡眠1毫秒!
96 執行緒睡眠1毫秒!
97 執行緒睡眠1毫秒!
98 執行緒睡眠1毫秒!
99 執行緒睡眠1毫秒!
2、執行緒的優先順序和執行緒讓步yield()
執行緒的讓步是通過Thread.yield()來實現的。yield()方法的作用是:暫停當前正在執行的執行緒物件,並執行其他執行緒。
要理解yield(),必須瞭解執行緒的優先順序的概念。執行緒總是存在優先順序,優先順序範圍在1~10之間。JVM執行緒排程程式是基於優先順序的搶先排程機制。在大多數情況下,當前執行的執行緒優先順序將大於或等於執行緒池中任何執行緒的優先順序。但這僅僅是大多數情況。
注意:當設計多執行緒應用程式的時候,一定不要依賴於執行緒的優先順序。因為執行緒排程優先順序操作是沒有保障的,只能把執行緒優先順序作用作為一種提高程式效率的方法,但是要保證程式不依賴這種操作。
當執行緒池中執行緒都具有相同的優先順序,排程程式的JVM實現自由選擇它喜歡的執行緒。這時候排程程式的操作有兩種可能:一是選擇一個執行緒執行,直到它阻塞或者執行完成為止。二是時間分片,為池內的每個執行緒提供均等的執行機會。
設定執行緒的優先順序:執行緒預設的優先順序是建立它的執行執行緒的優先順序。可以通過setPriority(int newPriority)更改執行緒的優先順序。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
執行緒優先順序為1~10之間的正整數,JVM從不會改變一個執行緒的優先順序。然而,1~10之間的值是沒有保證的。一些JVM可能不能識別10個不同的值,而將這些優先順序進行每兩個或多個合併,變成少於10個的優先順序,則兩個或多個優先順序的執行緒可能被對映為一個優先順序。
執行緒預設優先順序是5,Thread類中有三個常量,定義執行緒優先順序範圍:
static intMAX_PRIORITY:執行緒可以具有的最高優先順序。
static intMIN_PRIORITY:執行緒可以具有的最低優先順序。
static intNORM_PRIORITY:分配給執行緒的預設優先順序。
3、Thread.yield()方法
Thread.yield()方法作用是:暫停當前正在執行的執行緒物件,並執行其他執行緒。
yield()應該做的是讓當前執行執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。
結論:yield()從未導致執行緒轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。
4、join()方法
Thread的非靜態方法join()讓一個執行緒B“加入”到另外一個執行緒A的尾部。在A執行完畢之前,B不能工作。例如:
Thread t = new MyThread();
t.start();
t.join();
另外,join()方法還有帶超時限制的過載版本。例如t.join(5000);則讓執行緒等待5000毫秒,如果超過這個時間,則停止等待,變為可執行狀態。
執行緒的加入join()對執行緒棧導致的結果是執行緒棧發生了變化,當然這些變化都是瞬時的。下面給示意圖:
小結
到目前位置,介紹了執行緒離開執行狀態的3種方法:
1、呼叫Thread.sleep():使當前執行緒睡眠至少多少毫秒(儘管它可能在指定的時間之前被中斷)。
2、呼叫Thread.yield():不能保障太多事情,儘管通常它會讓當前執行執行緒回到可執行性狀態,使得有相同優先順序的執行緒有機會執行。
3、呼叫join()方法:保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。
除了以上三種方式外,還有下面幾種特殊情況可能使執行緒離開執行狀態:
1、執行緒的run()方法完成。
2、在物件上呼叫wait()方法(不是線上程上呼叫)。
3、執行緒不能在物件上獲得鎖定,它正試圖執行該物件的方法程式碼。
4、執行緒排程程式可以決定將當前執行狀態移動到可執行狀態,以便讓另一個執行緒獲得執行機會,而不需要任何理由。