Java中結束執行緒的三種方式
原文地址:http://blog.csdn.net/anhuidelinger/article/details/11746365
有三種方法可以使終止執行緒。
1. 使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止。
2. 使用stop方法強行終止執行緒(這個方法不推薦使用,因為stop和suspend、resume一樣,也可能發生不可預料的結果)。
3. 使用interrupt方法中斷執行緒。
1. 使用退出標誌終止執行緒
當run方法執行完後,執行緒就會退出。但有時run方法是永遠不會結束的。如在服務端程式中使用執行緒進行監聽客戶端請求,或是其他的需要迴圈處理的任務。在這種情況下,一般是將這些任務放在一個迴圈中,如while迴圈。如果想讓迴圈永遠執行下去,可以使用while(true){……}來處理。但要想使while迴圈在某一特定條件下退出,最直接的方法就是設一個boolean型別的標誌,並通過設定這個標誌為true或false來控制while迴圈是否退出。下面給出了一個利用退出標誌終止執行緒的例子。
- package chapter2;
- publicclass ThreadFlag extends Thread
- {
- publicvolatileboolean exit = false;
- publicvoid run()
- {
- while (!exit);
- }
- publicstaticvoid main(String[] args) throws Exception
- {
- ThreadFlag thread = new ThreadFlag();
- thread.start();
- sleep(5000); // 主執行緒延遲5秒
- thread.exit = true; // 終止執行緒thread
- thread.join();
- System.out.println(”執行緒退出!”);
- }
- }
在上面程式碼中定義了一個退出標誌exit,當exit為true時,while迴圈退出,exit的預設值為false.在定義exit時,使用了一個Java關鍵字volatile,這個關鍵字的目的是使exit同步,也就是說在同一時刻只能由一個執行緒來修改exit的值,package chapter2; public class ThreadFlag extends Thread { public volatile boolean exit = false; public void run() { while (!exit); } public static void main(String[] args) throws Exception { ThreadFlag thread = new ThreadFlag(); thread.start(); sleep(5000); // 主執行緒延遲5秒 thread.exit = true; // 終止執行緒thread thread.join(); System.out.println("執行緒退出!"); } }
2. 使用stop方法終止執行緒
使用stop方法可以強行終止正在執行或掛起的執行緒。我們可以使用如下的程式碼來終止執行緒:
[java] view plain copy print?
- thread.stop();
thread.stop();
雖然使用上面的程式碼可以終止執行緒,但使用stop方法是很危險的,就象突然關閉計算機電源,而不是按正常程式關機一樣,可能會產生不可預料的結果,因此,並不推薦使用stop方法來終止執行緒。 3. 使用interrupt方法終止執行緒
使用interrupt方法來終端執行緒可分為兩種情況:
(1)執行緒處於阻塞狀態,如使用了sleep方法。
(2)使用while(!isInterrupted()){……}來判斷執行緒是否被中斷。
在第一種情況下使用interrupt方法,sleep方法將丟擲一個InterruptedException例外,而在第二種情況下執行緒將直接退出。下面的程式碼演示了在第一種情況下使用interrupt方法。 [java] view plain copy print?
- package chapter2;
- publicclass ThreadInterrupt extends Thread
- {
- publicvoid run()
- {
- try
- {
- sleep(50000); // 延遲50秒
- }
- catch (InterruptedException e)
- {
- System.out.println(e.getMessage());
- }
- }
- publicstaticvoid main(String[] args) throws Exception
- {
- Thread thread = new ThreadInterrupt();
- thread.start();
- System.out.println(”在50秒之內按任意鍵中斷執行緒!”);
- System.in.read();
- thread.interrupt();
- thread.join();
- System.out.println(”執行緒已經退出!”);
- }
- }
package chapter2;
public class ThreadInterrupt extends Thread
{
public void run()
{
try
{
sleep(50000); // 延遲50秒
}
catch (InterruptedException e)
{
System.out.println(e.getMessage());
}
}
public static void main(String[] args) throws Exception
{
Thread thread = new ThreadInterrupt();
thread.start();
System.out.println("在50秒之內按任意鍵中斷執行緒!");
System.in.read();
thread.interrupt();
thread.join();
System.out.println("執行緒已經退出!");
}
}
上面程式碼的執行結果如下: [html] view plain copy print?
- 在50秒之內按任意鍵中斷執行緒!
- sleep interrupted
- 執行緒已經退出!
在50秒之內按任意鍵中斷執行緒!
sleep interrupted
執行緒已經退出!
在呼叫interrupt方法後, sleep方法丟擲異常,然後輸出錯誤資訊:sleep interrupted. 注意:在Thread類中有兩個方法可以判斷執行緒是否通過interrupt方法被終止。一個是靜態的方法interrupted(),一個是非靜態的方法isInterrupted(),這兩個方法的區別是interrupted用來判斷當前線是否被中斷,而isInterrupted可以用來判斷其他執行緒是否被中斷。因此,while (!isInterrupted())也可以換成while (!Thread.interrupted())。
如何停止java的執行緒一直是一個困惱我們開發多執行緒程式的一個問題。這個問題最終在Java5的java.util.concurrent中得到了回答:使用interrupt(),讓執行緒在run方法中停止。
簡介
建議使用的方法
[java] view plain copy print?- privatevolatile Thread blinker;
- publicvoid stop() {
- blinker = null;
- }
- publicvoid run() {
- Thread thisThread = Thread.currentThread();
- while (blinker == thisThread) {
- try {
- thisThread.sleep(interval);
- } catch (InterruptedException e){
- }
- repaint();
- }
- }
private volatile Thread blinker;
public void stop() {
blinker = null;
}
public void run() {
Thread thisThread = Thread.currentThread();
while (blinker == thisThread) {
try {
thisThread.sleep(interval);
} catch (InterruptedException e){
}
repaint();
}
}
當執行緒處於非執行(Run)狀態
當執行緒處於下面的狀況時,屬於非執行狀態:
-
當sleep方法被呼叫。
-
當wait方法被呼叫。
-
當被I/O阻塞,可能是檔案或者網路等等。
當執行緒處於上述的狀態時,使用前面介紹的方法就不可用了。這個時候,我們可以使用interrupt()來打破阻塞的情況,如:
[java] view plain copy print?- publicvoid stop() {
- Thread tmpBlinker = blinker;
- blinker = null;
- if (tmpBlinker != null) {
- tmpBlinker.interrupt();
- }
- }
public void stop() {
Thread tmpBlinker = blinker;
blinker = null;
if (tmpBlinker != null) {
tmpBlinker.interrupt();
}
}
[java] view plain copy print?- try {
- ….
- wait();
- } catch (InterruptedException iex) {
- thrownew RuntimeException(“Interrupted”,iex);
- }
try {
....
wait();
} catch (InterruptedException iex) {
throw new RuntimeException("Interrupted",iex);
}
阻塞的I/O
當執行緒被I/O阻塞的時候,呼叫interrupt()的情況是依賴於實際執行的平臺的。在Solaris和Linux平臺上將會丟擲InterruptedIOException的異常,但是Windows上面不會有這種異常。所以,我們處理這種問題不能依靠於平臺的實現。如:
[java] view plain copy print?- package com.cnblogs.gpcuster
- import java.net.*;
- import java.io.*;
- publicabstractclass InterruptibleReader extends Thread {
- private Object lock = new Object( );
- private InputStream is;
- privateboolean done;
- privateint buflen;
- protectedvoid processData(byte[] b, int n) { }
- class ReaderClass extends Thread {
- publicvoid run( ) {
- byte[] b = newbyte[buflen];
- while (!done) {
- try {
- int n = is.read(b, 0, buflen);
- processData(b, n);
- } catch (IOException ioe) {
- done = true;
- }
- }
- synchronized(lock) {
- lock.notify( );
- }
- }
- }
- public InterruptibleReader(InputStream is) {
- this(is, 512);
- }
- public InterruptibleReader(InputStream is, int len) {
- this.is = is;
- buflen = len;
- }
- publicvoid run( ) {
- ReaderClass rc = new ReaderClass( );
- synchronized(lock) {
- rc.start( );
- while (!done) {
- try {
- lock.wait( );
- } catch (InterruptedException ie) {
- done = true;
- rc.interrupt( );
- try {
- is.close( );
- } catch (IOException ioe) {}
- }
- }
- }
- }
- }
package com.cnblogs.gpcuster
import java.net.*;
import java.io.*;
public abstract class InterruptibleReader extends Thread {
private Object lock = new Object( );
private InputStream is;
private boolean done;
private int buflen;
protected void processData(byte[] b, int n) { }
class ReaderClass extends Thread {
public void run( ) {
byte[] b = new byte[buflen];
while (!done) {
try {
int n = is.read(b, 0, buflen);
processData(b, n);
} catch (IOException ioe) {
done = true;
}
}
synchronized(lock) {
lock.notify( );
}
}
}
public InterruptibleReader(InputStream is) {
this(is, 512);
}
public InterruptibleReader(InputStream is, int len) {
this.is = is;
buflen = len;
}
public void run( ) {
ReaderClass rc = new ReaderClass( );
synchronized(lock) {
rc.start( );
while (!done) {
try {
lock.wait( );
} catch (InterruptedException ie) {
done = true;
rc.interrupt( );
try {
is.close( );
} catch (IOException ioe) {}
}
}
}
}
}
[java] view plain copy print?- package com.cnblogs.gpcuster
- import java.io.BufferedReader;
- import java.io.FileDescriptor;
- import java.io.FileInputStream;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.nio.channels.Channels;
- publicclass InterruptInput {
- static BufferedReader in = new BufferedReader(
- new InputStreamReader(
- Channels.newInputStream(
- (new FileInputStream(FileDescriptor.in)).getChannel())));
- publicstaticvoid main(String args[]) {
- try {
- System.out.println(”Enter lines of input (user ctrl+Z Enter to terminate):”);
- System.out.println(”(Input thread will be interrupted in 10 sec.)”);
- // interrupt input in 10 sec
- (new TimeOut()).start();
- String line = null;
- while ((line = in.readLine()) != null) {
- System.out.println(”Read line:’”+line+“’”);
- }
- } catch (Exception ex) {
- System.out.println(ex.toString()); // printStackTrace();
- }
- }
- publicstaticclass TimeOut extends Thread {
- int sleepTime = 10000;
- Thread threadToInterrupt = null;
- public TimeOut() {
- // interrupt thread that creates this TimeOut.
- threadToInterrupt = Thread.currentThread();
- setDaemon(true);
- }
- publicvoid run() {
- try {
- sleep(10000); // wait 10 sec
- } catch(InterruptedException ex) {/*ignore*/}
- threadToInterrupt.interrupt();
- }
- }
- }
package com.cnblogs.gpcuster
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;
public class InterruptInput {
static BufferedReader in = new BufferedReader(
new InputStreamReader(
Channels.newInputStream(
(new FileInputStream(FileDescriptor.in)).getChannel())));
public static void main(String args[]) {
try {
System.out.println("Enter lines of input (user ctrl+Z Enter to terminate):");
System.out.println("(Input thread will be interrupted in 10 sec.)");
// interrupt input in 10 sec
(new TimeOut()).start();
String line = null;
while ((line = in.readLine()) != null) {
System.out.println("Read line:'"+line+"'");
}
} catch (Exception ex) {
System.out.println(ex.toString()); // printStackTrace();
}
}
public static class TimeOut extends Thread {
int sleepTime = 10000;
Thread threadToInterrupt = null;
public TimeOut() {
// interrupt thread that creates this TimeOut.
threadToInterrupt = Thread.currentThread();
setDaemon(true);
}
public void run() {
try {
sleep(10000); // wait 10 sec
} catch(InterruptedException ex) {/*ignore*/}
threadToInterrupt.interrupt();
}
}
}
這裡還需要注意一點,當執行緒處於寫檔案的狀態時,呼叫interrupt()不會中斷執行緒。
不提倡的stop()方法
臭名昭著的stop()停止執行緒的方法已不提倡使用了,原因是什麼呢?
當在一個執行緒物件上呼叫stop()方法時,這個執行緒物件所執行的執行緒就會立即停止,並丟擲特殊的ThreadDeath()異常。這裡的“立即”因為太“立即”了,
假如一個執行緒正在執行:
- synchronizedvoid {
- x = 3;
- y = 4;
- }
synchronized void {
x = 3;
y = 4;
}
由於方法是同步的,多個執行緒訪問時總能保證x,y被同時賦值,而如果一個執行緒正在執行到x = 3;時,被呼叫了 stop()方法,即使在同步塊中,它也乾脆地stop了,這樣就產生了不完整的殘廢資料。而多執行緒程式設計中最最基礎的條件要保證資料的完整性,所以請忘記執行緒的stop方法,以後我們再也不要說“停止執行緒”了。
如何才能“結束”一個執行緒?
interupt()中斷執行緒
- 正常執行.
- 處理結束前的工作,也就是準備結束.
- 結束退出.
那麼如何讓一個執行緒結束呢?既然不能呼叫stop,可用的只的interrupt()方法。但interrupt()方法只是改變了執行緒的執行狀態,如何讓它退出執行?對於一般邏輯,只要執行緒狀態已經中斷,我們就可以讓它退出,這裡我們定義一個執行緒類ThreadA,所以這樣的語句可以保證執行緒在中斷後就能結束執行:
[java] view plain copy print?- while(!isInterrupted()){
- 正常邏輯
- }
while(!isInterrupted()){
正常邏輯
}
一個測試類,ThreadDemo這樣ThreadDemo呼叫interrupt()方法,isInterrupted()為true,就會退出執行。但是如果執行緒正在執行wait,sleep,join方法,你呼叫interrupt()方法,這個邏輯就不完全了。
我們可以這樣處理:
[java] view plain copy print?- publicvoid run(){
- while(!isInterrupted()){
- try{
- 正常工作
- }catch(InterruptedException e){
- //nothing
- }
- }
- }
public void run(){
while(!isInterrupted()){
try{
正常工作
}catch(InterruptedException e){
//nothing
}
}
}
}
想一想,如果一個正在sleep的執行緒,在呼叫interrupt後,會如何?wait方法檢查到isInterrupted()為true,丟擲異常, 而你又沒有處理。而一個丟擲了InterruptedException的執行緒的狀態馬上就會被置為非中斷狀態,如果catch語句沒有處理異常,則下一 次迴圈中isInterrupted()為false,執行緒會繼續執行,可能你N次丟擲異常,也無法讓執行緒停止。這個錯誤情況的例項程式碼
ThreadA
[java] view plain copy print?- publicclass ThreadA extends Thread {
- int count=0;
- publicvoid run(){
- System.out.println(getName()+”將要執行…”);
- while(!this.isInterrupted()){
- System.out.println(getName()+”執行中”+count++);
- try{
- Thread.sleep(400);
- }catch(InterruptedException e){
- System.out.println(getName()+”從阻塞中退出…”);
- System.out.println(”this.isInterrupted()=”+this.isInterrupted());
- }
- }
- System.out.println(getName()+”已經終止!”);
- }
- }
public class ThreadA extends Thread {
int count=0;
public void run(){
System.out.println(getName()+"將要執行...");
while(!this.isInterrupted()){
System.out.println(getName()+"執行中"+count++);
try{
Thread.sleep(400);
}catch(InterruptedException e){
System.out.println(getName()+"從阻塞中退出...");
System.out.println("this.isInterrupted()="+this.isInterrupted());
}
}
System.out.println(getName()+"已經終止!");
}
}
ThreadDemo [java] view plain copy print?
- publicclass ThreadDemo {
- publicstaticvoid main(String argv[])throws InterruptedException{
- ThreadA ta=new ThreadA();
- ta.setName(”ThreadA”);
- ta.start();
- Thread.sleep(2000);
- System.out.println(ta.getName()+”正在被中斷…”);
- ta.interrupt();
- System.out.println(”ta.isInterrupted()=”+ta.isInterrupted());
- }
- }
public class ThreadDemo {
public static void main(String argv[])throws InterruptedException{
ThreadA ta=new ThreadA();
ta.setName("ThreadA");
ta.start();
Thread.sleep(2000);
System.out.println(ta.getName()+"正在被中斷...");
ta.interrupt();
System.out.println("ta.isInterrupted()="+ta.isInterrupted());
}
}
那麼如何能確保執行緒真正停止?線上程同步的時候我們有一個叫“二次惰性檢測”(double check),能在提高效率的基礎上又確保執行緒真正中同步控制中。那麼我把執行緒正確退出的方法稱為“雙重安全退出”,即不以isInterrupted ()為迴圈條件。而以一個標記作為迴圈條件:正確的ThreadA程式碼是:
[java] view plain copy print?- publicclass ThreadA extends Thread {
- privateboolean isInterrupted=false;
- int count=0;
- publicvoid interrupt(){
- isInterrupted = true;
- super.interrupt();
- }
- publicvoid run(){
- System.out.println(getName()+”將要執行…”);
- while(!isInterrupted){
- System.out.println(getName()+”執行中”+count++);
- try{
- Thread.sleep(400);
- }catch(InterruptedException e){
- System.out.println(getName()+”從阻塞中退出…”);
- System.out.println(”this.isInterrupted()=”+this.isInterrupted());
- }
- }
- System.out.println(getName()+”已經終止!”);
- }
- }
public class ThreadA extends Thread {
private boolean isInterrupted=false;
int count=0;
public void interrupt(){
isInterrupted = true;
super.interrupt();
}
public void run(){
System.out.println(getName()+"將要執行...");
while(!isInterrupted){
System.out.println(getName()+"執行中"+count++);
try{
Thread.sleep(400);
}catch(InterruptedException e){
System.out.println(getName()+"從阻塞中退出...");
System.out.println("this.isInterrupted()="+this.isInterrupted());
}
}
System.out.println(getName()+"已經終止!");
}
}
在java多執行緒程式設計中,執行緒的終止可以說是一個必然會遇到的操作。但是這樣一個常見的操作其實並不是一個能夠輕而易舉實現的操作,而且在某些場景下情況會變得更復雜更棘手。
Java標準API中的Thread類提供了stop方法可以終止執行緒,但是很遺憾,這種方法不建議使用,原因是這種方式終止執行緒中斷臨界區程式碼執行,並會釋放執行緒之前獲取的監控器鎖,這樣勢必引起某些物件狀態的不一致(因為臨界區程式碼一般是原子的,不會被幹擾的)。這樣一來,就必須根據執行緒的特點使用不同的替代方案以終止執行緒。根據停止執行緒時執行緒執行狀態的不同有如下停止執行緒的方法。
1 處於執行狀態的執行緒停止
處於執行狀態的執行緒就是常見的處於一個迴圈中不斷執行業務流程的執行緒,這樣的執行緒需要通過設定停止變數的方式,在每次迴圈開始處判斷變數是否改變為停止,以達到停止執行緒的目的,比如如下程式碼框架:
[java] view plain copy print?- privatevolatile Thread blinker;
- publicvoid stop() {
- blinker = null;
- }
- publicvoid run() {
- Thread thisThread = Thread.currentThread();
- while (blinker == thisThread) {
- try {
- //業務流程
- } catch (Exception e){}
- }
- }
private volatile Thread blinker;
public void stop() {
blinker = null;
}
public void run() {
Thread thisThread = Thread.currentThread();
while (blinker == thisThread) {
try {
//業務流程
} catch (Exception e){}
}
}
如果主執行緒呼叫該執行緒物件的stop方法,blinker物件被設定為null,則執行緒的下次迴圈中blinker!=thisThread,因而可以退出迴圈,並退出run方法而使執行緒結束。將引用變數blinker的型別前加上volatile關鍵字的目的是防止編譯器對該變數存取時的優化,這種優化主要是快取對變數的修改,這將使其他執行緒不會立刻看到修改後的blinker值,從而影響退出。此外,Java標準保證被volatile修飾的變數的讀寫都是原子的。
上述的Thread型別的blinker完全可以由更為簡單的boolean型別變數代替。
2 即將或正在處於非執行態的執行緒停止
執行緒的非執行狀態常見的有如下兩種情況:
可中斷等待:執行緒呼叫了sleep或wait方法,這些方法可丟擲InterruptedException;
Io阻塞:執行緒呼叫了IO的read操作或者socket的accept操作,處於阻塞狀態。
2.1 處於可中斷等待執行緒的停止
如果執行緒呼叫了可中斷等待方法,正處於等待狀態,則可以通過呼叫Thread的interrupt方法讓等待方法丟擲InterruptedException異常,然後在迴圈外截獲並處理異常,這樣便跳出了執行緒run方法中的迴圈,以使執行緒順利結束。
上述的stop方法中需要做的修改就是在設定停止變數之後呼叫interrupt方法:
[java] view plain copy print?- privatevolatile Thread blinker;
- publicvoid stop() {
- Thread tmp = blinker;
- blinker = null;
- if(tmp!=null){
- tmp.interrupt();
- }
- }
private volatile Thread blinker;
public void stop() {
Thread tmp = blinker;
blinker = null;
if(tmp!=null){
tmp.interrupt();
}
}
特別的,Thread物件的interrupt方法會設定執行緒的interruptedFlag,所以我們可以通過判斷Thread物件的isInterrupted方法的返回值來判斷是否應該繼續run方法內的迴圈,從而代替執行緒中的volatile停止變數。這時的上述run方法的程式碼框架就變為如下:
[java] view plain copy print?- publicvoid run() {
- while (!Thread.currentThread().isInterrupted()) {
- try {
- //業務流程
- } catch (Exception e){}
- }
- }
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
//業務流程
} catch (Exception e){}
}
}
需要注意的是Thread物件的isInterrupted不會清除interrupted標記,但是Thread物件的interrupted方法(與interrupt方法區別)會清除該標記。
2.2 處於IO阻塞狀態執行緒的停止
Java中的輸入輸出流並沒有類似於Interrupt的機制,但是Java的InterruptableChanel介面提供了這樣的機制,任何實現了InterruptableChanel介面的類的IO阻塞都是可中斷的,中斷時丟擲ClosedByInterruptedException,也是由Thread物件呼叫Interrupt方法完成中斷呼叫。IO中斷後將關閉通道。
以檔案IO為例,構造一個可中斷的檔案輸入流的程式碼如下:
[java] view plain copy print?- new InputStreamReader(
- Channels.newInputStream(
- (new FileInputStream(FileDescriptor.in)).getChannel())));
new InputStreamReader(
Channels.newInputStream(
(new FileInputStream(FileDescriptor.in)).getChannel())));
實現InterruptableChanel介面的類包括FileChannel,ServerSocketChannel, SocketChannel, Pipe.SinkChannel andPipe.SourceChannel,也就是說,原則上可以實現檔案、Socket、管道的可中斷IO阻塞操作。
雖然解除IO阻塞的方法還可以直接呼叫IO物件的Close方法,這也會丟擲IO異常。但是InterruptableChanel機制能夠使處於IO阻塞的執行緒能夠有一個和處於中斷等待的執行緒一致的執行緒停止方案。
3 處於大資料IO讀寫中的執行緒停止
處於大資料IO讀寫中的執行緒實際上處於執行狀態,而不是等待或阻塞狀態,因此上面的interrupt機制不適用。執行緒處於IO讀寫中可以看成是執行緒執行中的一種特例。停止這樣的執行緒的辦法是強行close掉io輸入輸出流物件,使其丟擲異常,進而使執行緒停止。
最好的建議是將大資料的IO讀寫操作放在迴圈中進行,這樣可以在每次迴圈中都有執行緒停止的時機,這也就將問題轉化為如何停止正在執行中的執行緒的問題了。
4 線上程執行前停止執行緒
有時,執行緒中的run方法需要足夠健壯以支援線上程實際執行前終止執行緒的情況。即在Thread建立後,到Thread的start方法呼叫前這段時間,呼叫自定義的stop方法也要奏效。從上述的停止處於等待狀態執行緒的程式碼示例中,stop方法並不能終止執行前的執行緒,因為在Thread的start方法被呼叫前,呼叫interrupt方法並不會將Thread物件的中斷狀態置位,這樣當run方法執行時,currentThread的isInterrupted方法返回false,執行緒將繼續執行下去。
為了解決這個問題,不得不自己再額外建立一個volatile標誌量,並將其加入run方法的最開頭:
[java] view pl