使用Thread.interrupt()中斷線程
正如Listing A中所描述的,Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait, Thread.join和 Thread.sleep三種方法之一阻塞,那么,它將接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態。
因此,如果線程被上述幾種方法阻塞,正確的停止線程方式是設置共享變量,並調用interrupt()(注意變量應該先設置)。如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就將得到異常(該線程必須事先預備好處理此狀況),接着逃離阻塞狀態。在任何一種情況中,最后線程都將檢查共享變量然后再停止。Listing C這個示例描述了該技術。
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
|
Listing C
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..."
);
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(
1000
);
}
catch
(InterruptedException e) {
System.out.println(
"Thread interrupted..."
);
}
}
System.out.println(
"Thread exiting under request..."
);
}
}
|
一旦Listing C中的Thread.interrupt()被調用,線程便收到一個異常,於是逃離了阻塞狀態並確定應該停止。運行以上代碼將得到下面的輸出:
1
2
3
4
5
6
7
8
|
Starting thread...
Thread running...
Thread running...
Thread running...
Asking thread to stop...
Thread interrupted...
Thread exiting under request...
Stopping application...
|
中斷I/O操作
然而,如果線程在I/O操作進行時被阻塞,又會如何?I/O操作可以阻塞線程一段相當長的時間,特別是牽扯到網絡應用時。例如,服務器可能需要等待一個請求(request),又或者,一個網絡應用程序可能要等待遠端主機的響應。
如果你正使用通道(channels)(這是在Java 1.4中引入的新的I/O API),那么被阻塞的線程將收到一個 ClosedByInterruptException異常。如果情況是這樣,其代碼的邏輯和第三個例子中的是一樣的,只是異常不同而已。
但是,你可能正使用Java1.0之前就存在的傳統的I/O,而且要求更多的工作。既然這樣,Thread.interrupt()將不起作用,因為線程將不會退出被阻塞狀態。Listing D描述了這一行為。盡管interrupt()被調用,線程也不會退出被阻塞狀態。
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
|
Listing D
import
java.io.*;
class
Example4
extends
Thread {
public
static
void
main( String args[] )
throws
Exception {
Example4 thread =
new
Example4();
System.out.println(
"Starting thread..."
);
thread.start();
Thread.sleep(
3000
);
System.out.println(
"Interrupting thread..."
);
thread.interrupt();
Thread.sleep(
3000
);
System.out.println(
"Stopping application..."
);
//System.exit( 0 );
}
public
void
run() {
ServerSocket socket;
try
{
socket =
new
ServerSocket(
7856
);
}
catch
(IOException e) {
System.out.println(
"Could not create the socket..."
);
return
;
}
while
(
true
) {
System.out.println(
"Waiting for connection..."
);
try
{
Socket sock = socket.accept();
}
catch
(IOException e) {
System.out.println(
"accept() failed or interrupted..."
);
}
}
}
}
|
很幸運,Java平台為這種情形提供了一項解決方案,即調用阻塞該線程的套接字的close()方法。在這種情形下,如果線程被I/O操作阻塞,該線程將接收到一個SocketException異常,這與使用interrupt()方法引起一個InterruptedException異常被拋出非常相似。
唯一要說明的是,必須存在socket的引用(reference),只有這樣close()方法才能被調用。這意味着socket對象必須被共享。Listing E描述了這一情形。運行邏輯和以前的示例是相同的。
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
|
Listing E
import
java.net.*;
import
java.io.*;
class
Example5
extends
Thread {
volatile
boolean
stop =
false
;
volatile
ServerSocket socket;
public
static
void
main(String args[])
throws
Exception {
Example5 thread =
new
Example5();
System.out.println(
"Starting thread..."
);
thread.start();
Thread.sleep(
3000
);
System.out.println(
"Asking thread to stop..."
);
thread.stop =
true
;
thread.socket.close();
Thread.sleep(
3000
);
System.out.println(
"Stopping application..."
);
//System.exit( 0 );
}
public
void
run() {
try
{
socket =
new
ServerSocket(
7856
);
}
catch
(IOException e) {
System.out.println(
"Could not create the socket..."
);
return
;
}
while
(!stop) {
System.out.println(
"Waiting for connection..."
);
try
{
Socket sock = socket.accept();
}
catch
(IOException e) {
System.out.println(
"accept() failed or interrupted..."
);
}
}
System.out.println(
"Thread exiting under request..."
);
}
}
|
以下是運行Listing E中代碼后的輸出:
1
2
3
4
5
6
|
Starting thread...
Waiting
for
connection...
Asking thread to stop...
accept() failed or interrupted...
Thread exiting under request...
Stopping application...
|
多線程是一個強大的工具,然而它正呈現出一系列難題。其中之一是如何中斷一個正在運行的線程。如果恰當地實現,使用上述技術中斷線程將比使用Java平台上已經提供的內嵌操作更為簡單。
[線程的interrupt()方法,interrupted()和isInterrupted()] 的區別
這三個方法是關系非常密切而且又比較復雜的,雖然它們各自的功能很清楚,但它們之間的關系有大多數 人不是真正的了解.
interrupt()方法,它是實例方法,而它也是最奇怪的方法,在java語言中,線程最初被設計為 "隱晦難懂 " 的東西,直到現在它的語義不沒有象它的名字那樣准確.
大多數人以為,一個線程象調用了interrupt()方法,那它對應的線程就應該被中斷而拋出異常, 事實中,當一個線程對象調用interrupt()方法,它對應的線程並沒有被中斷,只是改變了它的中斷狀態. 使當前線程的狀態變以中斷狀態,如果沒有其它影響,線程還會自己繼續執行. 只有當線程執行到sleep,wait,join等方法時,或者自己檢查中斷狀態而拋出異常的情況下,線程 才會拋出異常.
如果線程對象調用interrupt()后它對應的線程就立即中斷,那么interrupted()方法就不可能執行. 因為interrupted()方法是一個static方法,就是說只能在當前線程上調用,而如果一個線程interrupt()后它已經中斷了,那它又如何讓自己interrupted()?
正因為一個線程調用interrupt()后只是改變了中斷狀態,它可以繼續執行下去,在沒有調用sleep, wait,join等法或自己拋出異常之前,它就可以調用interrupted()來清除中斷狀態(還會原狀)
interrupted()方法會檢查當前線程的中斷狀態,如果為 "被中斷狀態 "則改變當前線程為 "非中斷狀態 "並返回true,如果為 "非中斷狀態 "則返回false,它不僅檢查當前線程是否為中斷狀態,而且在保證當前線程回來非中斷狀態,所以它叫 "interrupted ",是說中斷的狀態已經結束(到非中斷狀態了)
isInterrupted()方法則僅僅檢查線程對象對應的線程是否是中斷狀態,並不改變它的狀態.
目前大家只能先記住這三個方法的功能,只有真正深入到多線程編程實踐中,才會體會到它們為什么 是對象方法,為什么是類方法.