長期以來,多線程問題頗爲受到面試官的青睞。雖然我個人認爲我們當中很少有人能真正獲得機會開發複雜的多線程應用(在過去的七年中,我得到了一個機會),但是理解多線程對增加你的信心很有用。之前,我討論了一個wait()和sleep()方法區別的問題,這一次,我將會討論join()和yield()方法的區別。坦白的說,實際上我並沒有用過其中任何一個方法,所以,如果你感覺有不恰當的地方,請提出討論。
Java線程調度的一點背景
在各種各樣的線程中,Java虛擬機必須實現一個有優先權的、基於優先級的調度程序。這意味着Java程序中的每一個線程被分配到一定的優先權,使用定義好的範圍內的一個正整數表示。優先級可以被開發者改變。即使線程已經運行了一定時間,Java虛擬機也不會改變其優先級
優先級的值很重要,因爲Java虛擬機和下層的操作系統之間的約定是操作系統必須選擇有最高優先權的Java線程運行。所以我們說Java實現了一個基於優先權的調度程序。該調度程序使用一種有優先權的方式實現,這意味着當一個有更高優先權的線程到來時,無論低優先級的線程是否在運行,都會中斷(搶佔)它。這個約定對於操作系統來說並不總是這樣,這意味着操作系統有時可能會選擇運行一個更低優先級的線程。(我憎恨多線程的這一點,因爲這不能保證任何事情)
注意Java並不限定線程是以時間片運行,但是大多數操作系統卻有這樣的要求。在術語中經常引起混淆:搶佔經常與時間片混淆。事實上,搶佔意味着只有擁有高優先級的線程可以優先於低優先級的線程執行,但是當線程擁有相同優先級的時候,他們不能相互搶佔。它們通常受時間片管制,但這並不是Java的要求。
理解線程的優先權
接下來,理解線程優先級是多線程學習很重要的一步,尤其是瞭解yield()函數的工作過程。
- 記住當線程的優先級沒有指定時,所有線程都攜帶普通優先級。
- 優先級可以用從1到10的範圍指定。10表示最高優先級,1表示最低優先級,5是普通優先級。
- 記住優先級最高的線程在執行時被給予優先。但是不能保證線程在啓動時就進入運行狀態。
- 與在線程池中等待運行機會的線程相比,當前正在運行的線程可能總是擁有更高的優先級。
- 由調度程序決定哪一個線程被執行。
- t.setPriority()用來設定線程的優先級。
- 記住在線程開始方法被調用之前,線程的優先級應該被設定。
- 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY來設定優先級
現在,當我們對線程調度和線程優先級有一定理解後,讓我們進入主題。
yield()方法
理論上,yield意味着放手,放棄,投降。一個調用yield()方法的線程告訴虛擬機它樂意讓其他線程佔用自己的位置。這表明該線程沒有在做一些緊急的事情。注意,這僅是一個暗示,並不能保證不會產生任何影響。
在Thread.java中yield()定義如下:
1
2
3
4
5
6
7
|
/** *
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore *
this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU. *
Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect. */ public
static
native
void
yield(); |
讓我們列舉一下關於以上定義重要的幾點:
- Yield是一個靜態的原生(native)方法
- Yield告訴當前正在執行的線程把運行機會交給線程池中擁有相同優先級的線程。
- Yield不能保證使得當前正在運行的線程迅速轉換到可運行的狀態
- 它僅能使一個線程從運行狀態轉到可運行狀態,而不是等待或阻塞狀態
yield()方法使用示例
在下面的示例程序中,我隨意的創建了名爲生產者和消費者的兩個線程。生產者設定爲最小優先級,消費者設定爲最高優先級。在Thread.yield()註釋和非註釋的情況下我將分別運行該程序。沒有調用yield()方法時,雖然輸出有時改變,但是通常消費者行先打印出來,然後事生產者。
調用yield()方法時,兩個線程依次打印,然後將執行機會交給對方,一直這樣進行下去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package
test.core.threads; public
class
YieldExample { public
static
void
main(String[] args) { Thread
producer = new
Producer(); Thread
consumer = new
Consumer(); producer.setPriority(Thread.MIN_PRIORITY);
//Min
Priority consumer.setPriority(Thread.MAX_PRIORITY);
//Max
Priority producer.start(); consumer.start(); } } class
Producer extends
Thread { public
void
run() { for
( int
i = 0 ;
i < 5 ;
i++) { System.out.println( "I
am Producer : Produced Item "
+ i); Thread.yield(); } } } class
Consumer extends
Thread { public
void
run() { for
( int
i = 0 ;
i < 5 ;
i++) { System.out.println( "I
am Consumer : Consumed Item "
+ i); Thread.yield(); } } } |
上述程序在沒有調用yield()方法情況下的輸出:
1
2
3
4
5
6
7
8
9
10
|
I
am Consumer : Consumed Item 0 I
am Consumer : Consumed Item 1 I
am Consumer : Consumed Item 2 I
am Consumer : Consumed Item 3 I
am Consumer : Consumed Item 4 I
am Producer : Produced Item 0 I
am Producer : Produced Item 1 I
am Producer : Produced Item 2 I
am Producer : Produced Item 3 I
am Producer : Produced Item 4 |
上述程序在調用yield()方法情況下的輸出:
1
2
3
4
5
6
7
8
9
10
|
I
am Producer : Produced Item 0 I
am Consumer : Consumed Item 0 I
am Producer : Produced Item 1 I
am Consumer : Consumed Item 1 I
am Producer : Produced Item 2 I
am Consumer : Consumed Item 2 I
am Producer : Produced Item 3 I
am Consumer : Consumed Item 3 I
am Producer : Produced Item 4 I
am Consumer : Consumed Item 4 |
join()方法
線程實例的方法join()方法可以使得一個線程在另一個線程結束後再執行。如果join()方法在一個線程實例上調用,當前運行着的線程將阻塞直到這個線程實例完成了執行。
1
2
3
|
//Waits
for this thread to die. public
final
void
join() throws
InterruptedException |
在join()方法內設定超時,使得join()方法的影響在特定超時後無效。當超時時,主方法和任務線程申請運行的時候是平等的。然而,當涉及sleep時,join()方法依靠操作系統計時,所以你不應該假定join()方法將會等待你指定的時間。
像sleep,join通過拋出InterruptedException對中斷做出迴應。
join()方法使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package
test.core.threads; public
class
JoinExample { public
static
void
main(String[] args) throws
InterruptedException { Thread
t = new
Thread( new
Runnable() { public
void
run() { System.out.println( "First
task started" ); System.out.println( "Sleeping
for 2 seconds" ); try { Thread.sleep( 2000 ); }
catch
(InterruptedException e) { e.printStackTrace(); } System.out.println( "First
task completed" ); } }); Thread
t1 = new
Thread( new
Runnable() { public
void
run() { System.out.println( "Second
task completed" ); } }); t.start();
//
Line 15 t.join();
//
Line 16 t1.start(); } } Output: First
task started Sleeping
for
2
seconds First
task completed Second
task completed |
這是一些很小卻很重要的概念。在評論部分讓我知道你的想法。