C語言中文網 目錄

Java停止(終止)線程詳解版

停止線程是在多線程開發中很重要的技術點,掌握此技術可以對線程的停止進行有效的處理。停止線程在 Java 語言中并不像 break 語句那樣干脆,需要一些技巧性的處理。

使用 Java 內置支持多線程的類設計多線程應用是很常見的事情,然而,多線程給開發人員帶來了一些新的挑戰,如果處理不好就會導致超出預期的行為并且難以定位錯誤。

本節將討論如何更好地停止一個線程。停止一個線程意味著在線程處理完任務之前停掉正在做的操作,也就是放棄當前的操作。雖然這看起來非常簡單,但是必須做好防范措施,以便達到預期的效果。

停止一個線程可以使用 Threadstop() 方法,但最好不用它。雖然它確實可以停止一個正在運行的線程,但是這個方法是不安全的,而且已被棄用作廢了,在將來的 Java 版本中,這個方法將不可用或不被支持。

大多數停止一個線程的操作使用 Thread.interrupt() 方法,盡管方法的名稱是“停止,中止”的意思,但這個方法不會終止一個正在運行的線程,還需要加入一個判斷才可以完成線程的停止。關于此知識點在后面有專門的章節進行介紹。

在 Java 中有以下 3 種方法可以終止正在運行的線程:
  1. 使用退出標識,使線程正常退出,也就是當 run() 方法完成后線程終止。
  2. 使用 stop() 方法強行終止線程,但是不推薦使用這個方法,因為 stop() 和 suspend() 及 resume() 一樣,都是作廢過期的方法,使用它們可能產生不可預料的結果。
  3. 使用 interrupt() 方法中斷線程。

停止不了的線程

interrupt() 方法的作用是用來停止線程,但 intermpt() 方法的使用效果并不像循環結構中 break 語句那樣,可以馬上停止循環。調用 intermpt() 方法僅僅是在當前線程中打了一個停止的標記,并不是真的停止線程。

例 1

下面通過一個案例演示 interrupt() 方法停止線程的用法。案例用到的線程非常簡單,僅僅是實現輸出從 1~10000 的整數,代碼如下:
package ch14;
public class MyThread13 extends Thread
{
    @Override 
    public void run()
    { 
        super.run(); 
        for (int i=0;i<10000;i++)
        { 
            System.out.println("i="+(i+1)); 
        } 
    } 
}

在調用 intermpt() 方法停止 MyThread13 線程之前,首先進行了一個 100 毫秒的休眠。主線程的代碼如下:
package ch14;
public class Test17
{
    public static void main(String[] args)
    { 
        try
        { 
            MyThread13 thread=new MyThread13();      //創建MyThread13線程類實例
            thread.start();    //啟動線程
            Thread.sleep(100);    //延時100毫秒
            thread.interrupt();    //停止線程
        }
        catch(InterruptedException e)
        { 
            System.out.println("main catch"); 
            e.printStackTrace(); 
        } 
    }
}

主線程的運行結果如下所示。從中可以看到,雖然在延時 100 毫秒后調用 intermpt() 方法停止了 thread 線程,但是該線程仍然執行完成輸出 10000 行信息。
i=1
i=2
...
i=9999
i=10000

判斷線程是不是停止狀態

在介紹如何停止線程的知識點前,先來看一下如何判斷線程的狀態是不是停止的。在 Java 的 SDK 中,Thread.java 類里提供了兩種方法。
  1. this.interrupted():測試當前線程是否已經中斷。
  2. this.islnterrupted():測試線程是否已經中斷。

那么這兩個方法有什么區別呢?先來看看 this.intermpted() 方法的解釋:測試當前線程是否已經中斷,當前線程是指運行 this.interrupted() 方法的線程。為了對此方法有更深入的了解,下面通過一個案例進行說明。

例 2

假設 MyThread14 線程類的代碼如下:
package ch14;
public class MyThread14 extends Thread
{
    @Override 
    public void run()
    { 
        super.run(); 
        for(int i=0;i<10000;i++)
        { 
            System.out.println("i="+(i+1)); 
        } 
    } 
}

主線程的代碼如下:
package ch14;
public class Test18
{
    public static void main(String[] args)
    {
        try
        {
            MyThread14 thread=new MyThread14();
            thread.start();    //啟動線程
            Thread.sleep(100);    //延時100毫秒
            thread.interrupt();    //停止線程
            //Thread.currentThread().interrupt();
            System.out.println("是否停止1?="+thread.interrupted());
            System.out.println("是否停止2?="+thread.interrupted());
        }
        catch(InterruptedException e)
        {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}

程序運行后的結果如下所示。
i=1
i=2
...
i=9999
i=10000
是否停止1?=false
是否停止2?=false
end!

在主線程中是在 thread 對象上調用以下代碼來停止 thread 對象所代表的線程。
thread.interrupt();

后面又使用以下代碼來判斷 thread 對象所代表的線程是否停止。
System.out.println("是否停止 1 ? ="+thread.interrupted());
System.out.println("是否停止 2 ? ="+thread.interrupted());

從控制臺打印的結果來看,線程并未停止,這也就證明了 interrupted() 方法的解釋:測試當前線程是否已經中斷。這個“當前線程”是 main,它從未中斷過,所以打印的結果是兩個 false。

那么如何使 main 線程產生中斷效果呢?再來看一下如下的代碼:
public static void main(String[] args)
{
    Thread.currentThread().interrupt();
    System.out.println(" 是否停止 1 ? ="+Thread.interrupted());
    System.out.println(" 是否停止 2 ? ="+Thread.interrupted());
    System.out.println("end!");
}

程序運行后的結果如下所示。
是否停止 1 ? =true
是否停止 2 ? =false end!

從上述的結果來看,intermpted() 方法的確用來判斷出當前線程是不是停止狀態。但為什么第二個布爾值是 false 呢?查看一下官方幫助文檔中對 interrupted() 方法的解釋如下(斜體顯示):
測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false(在第一次調用已清除了其中斷狀態之后,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。

文檔已經解釋得很詳細,intermpted() 方法具有清除狀態的功能,所以第二次調用 interrupted() 方法返回的值是 false。

介紹完 interrupted() 方法后再來看一下 isInterrupted() 方法。isInterrupted() 方法的聲明如下:
public boolean isInterrupted()

從聲明中可以看出 isIntermpted() 方法不是 static 的。仍然以 MyThread14 線程為例,這里使用 isInterrupted() 方法來判斷線程是否停止,具體代碼如下:
package ch14;
public class Test18
{
    public static void main(String[] args)
    {
        try
        {
            MyThread14 thread=new MyThread14();
            thread.start();
            Thread.sleep(100);
            thread.interrupt();
            System.out.println("是否停止1?="+thread.isInterrupted());
            System.out.println("是否停止2?="+thread.isInterrupted());
        }
        catch(InterruptedException e)
        {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}

程序運行結果如下所示。
i=498
是否停止1?=true
i=499
是否俜止2?=true
i=500
end!
i=501
i=502

從程序的運行結果中可以看到,isInterrupted() 方法并未清除狀態標識,所以打印了兩個 true。經過上面示例的驗證總結一下這兩個方法。
  1. this.interrupted():測試當前線程是否已經是中斷狀態,執行后具有將狀態標識清除為 false 的功能。
  2. this.islnterrupted():測試線程 Thread 對象是否已經是中斷狀態,但不清除狀態標識。

異常法停止線程

有了前面學習過的知識,就可在線程中用 for 語句來判斷線程是否為停止狀態,如果是停止狀態,則后面的代碼不再運行。

例 3

下面的線程類 MyThread15 演示了在線程中使用 for 循環,并在循環中調用 intermpted() 方法判斷線程是否停止。
package ch14;
public class MyThread15 extends Thread
{
    @Override
    public void run()
    {
        super.run();
        for(int i=0;i<500000;i++)
        {
            if(this.interrupted())
            {    //如果當前線程處于停止狀態
                System.out.println("已經是停止狀態了!我要退出了!");
                break;
            }
            System.out.println("i="+(i+1));
        }
    }
}

接下來編寫啟動 MyThread15 線程的代碼,主線程代碼如下:
package ch14;
public class Test19
{
    public static void main(String[] args)
    {
        try
        { 
            MyThread15 thread=new MyThread15(); 
            thread.start();    //啟動線程
            Thread.sleep(2000);    //延時2000毫秒
            thread.interrupt();    //停止線程
        }
        catch(InterruptedException e)
        {    //捕捉線程停止異常
            System.out.println("main catch"); 
            e.printStackTrace(); 
        } 
        System.out.println("end!");    //主線程結束時輸出
    }
}

上述代碼啟動 MyThread15 線程后延時 2000 毫秒,之后將線程停止。為避免主線程崩潰使用 catch 捕捉了 InterruptedException 異常,此時會輸出“main catch”。在主線程執行結束后會輸出“end!”。程序執行的輸出結果如下所示。
......
i=271597
i=271598
已經是停止狀態了!我要退出了!
end!

從程序執行的結果可以看到,在示例中雖然停止了線程,但如果 for 語句下面還有語句,還是會繼續運行的。

下面對 MyThread15 線程進行修改,如下所示是 run() 方法的代碼:
public void run()
{ 
    super.run(); 
    for(int i=0;i<500000;i++)
    { 
        if(this.interrupted())
        { 
            System.out.println("已經是停止狀態了!我要退出了!"); 
            break; 
        } 
        System.out.println("i="+(i+1)); 
    } 
    System.out.println("我被輸出,如果此代碼是for又繼續運行,線程并未停止!"); 
}

此時的運行效果如下所示,說明線程仍然在繼續運行。
......
i=233702
i=233703
end!
已經是停止狀態了!我要退出了!
我被輸出,如果此代碼是for又繼續運行,線程并未停止!

那該如何解決線程停止后,語句仍然繼續運行的問題呢?解決的辦法是在線程中捕捉線程停止異常,如下為修改后的 run() 方法代碼。
public void run()
{ 
    super.run(); 
    try
    { 
        for(int i=0;i<500000;i++)
        { 
            if(this.interrupted())
            { 
                System.out.println("已經是停止狀態了!我要退出了!"); 
                throw new InterruptedException(); 
            } 
            System.out.println("i=" + (i + 1)); 
        } 
        System.out.println("我在for下面"); 
    }
    catch(InterruptedException e)
    { 
        System.out.println("進MyThread15.java類run方法中的catch了!"); 
        e.printStackTrace(); 
    } 
}

再次運行程序,當線程處于停止狀態后,如果 for 循環中的代碼繼續執行將會拋出 InterruptedException 異常,運行結果如下所示。
......
i=251711
i=251712
i=251713
已經是停止狀態了!我要退出了!
end!
進MyThread15.java類run方法中的catch了!
java.lang.InterruptedException
    at text.MyThread.run(MyThread.java:16)

在休眠中停止

如果線程在 sleep() 狀態下停止,會是什么效果呢?

例 4

下面通過一個案例來演示這種情況。如下所示為案例中使用的 MyThread16 線程類代碼。
package ch14;
public class MyThread16 extends Thread
{
    @Override
    public void run()
    {
        super.run();
        try
        {
            System.out.println("run begin");
            Thread.sleep(200000);
            System.out.println("run end");
        }
        catch(InterruptedException e)
        {
            System.out.println("在休眠中被停止!進入catch!"+this.isInterrupted());
            e.printStackTrace();
        }
    }
}

調用 MyThread16 線程的主線程代碼如下:
package ch14;
public class Test20
{
    public static void main(String[] args)
    {
        try
        {
            MyThread16 thread=new MyThread16();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        }
        catch(InterruptedException e)
        {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}

在上述代碼中啟動 MyThread16 線程后休眠了 200 毫秒,之后調用 interrupt() 方法停止線程,運行結果如下所示。
run begin
end!
在休眠中被停止!進入catch!false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at text.MyThread.run(MyThread.java:12)

從運行結果來看,如果在休眠狀態下停止某一線程則會拋出進入 InterruptedException 異常,所以會進入 catch 語句塊清除停止狀態值,使之變成 false。

例 5

這個示例是先休眠再停止線程,下面再編寫一個案例來演示先停止再休眠線程的情況。案例使用的 MyThread17 線程類代碼如下:
package ch14;
public class MyThread17 extends Thread
{
    @Override
    public void run()
    {
        super.run();
        try
        {
            for(int i=0;i<1000;i++)
            {
                System.out.println("i="+(i+1));
            }
            System.out.println("run begin");
            Thread.sleep(200);
            System.out.println("run end");
        }
        catch(InterruptedException e)
        {
            System.out.println("先停止,再遇到了sleep!進入catch!");
            e.printStackTrace();
        }
    }
}

使用 MyThread17 線程的主線程代碼如下:
package ch14;
public class Test21
{
    public static void main(String[] args)
    {
        MyThread17 thread=new MyThread17();
        thread.start();
        thread.interrupt();
        System.out.println("end!");
    }
}

在上述代碼中啟動 MyThread17 線程后沒有進行延時,馬上調用 interrupt() 方法進行停止線程,但是在 MyThread17 線程中有一個 200 毫秒的延時。運行程序后,首先會看到下所示的輸出,說明主線程執行完畢。
end!
i=1
i=2
i=3
i=4
i=5
i=6
......

稍等片刻后,將會看到如下所示的異常,說明線程停止了。
......
i=999
i=1000
run begin
先停止,再遇到了sleep!進入catch!
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at text.MyThread.run(MyThread.java:16)

強制停止線程

調用 stop() 方法可以在任意情況下強制停止一個線程。下面通過一個案例來演示 stop() 停止線程的方法。
package ch14;
public class MyThread18 extends Thread
{
    private int i=0;
    @Override
    public void run()
    {
        try
        {
            while (true)
            {
                i++;
                System.out.println("i=" + i);
                Thread.sleep(1000);
            }
        }
        catch(InterruptedException e)
        {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

如上述代碼所示,MyThread18 線程中包含一個死循環,該循環每隔 1000 毫秒執行一次,每次將 i 的值遞增 1 并輸出。

調用 MyThread18 線程的主線程代碼如下:
package ch14;
public class Test22
{
    @SuppressWarnings("deprecation")
    public static void main(String[] args)
    {
        try
        {
            MyThread18 thread=new MyThread18();
            thread.start();
            Thread.sleep(8000);
            thread.stop();
        }
        catch(InterruptedException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

如上述代碼所示,MyThread18 線程在啟動后有一個 8000 毫秒的延時,在這段時間內會循環 9 次,之后 stop() 方法被執行從而線程停止。運行后的輸出如下所示。
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9

注意:調用 stop() 方法時會拋出 java.lang.ThreadDeath 異常,但在通常情況下,此異常不需要顯式地捕捉。

釋放鎖的不良后果

從 JDK 1.6 以后 stop() 方法已經被作廢,因為如果強制讓線程停止則有可能使一些清理性的工作得不到完成。另外一個情況就是對鎖定的對象進行了“解鎖”,導致數據得不到同步的處理,出現數據不一致的問題。

使用 stop() 釋放鎖將會給數據造成不一致性的結果。如果出現這樣的情況,程序處理的數據就有可能遭到破壞,最終導致程序執行的流程錯誤,一定要特別注意。

例 6

下面通過一個案例來演示這種情況。案例使用了一個名為 SynchronizedObject 的實體類,該類代碼如下:
package ch14;
public class SynchronizedObject
{
    private String username="root";
    private String password="root";
    public String getUsername()
    {
        return username;
    }
    public void setUsername(String username)
    {
        this.username=username;
    }
    public String getPassword()
    {
        return password;
    }
    public void setPassword(String password)
    {
        this.password=password;
    }
    synchronized public void printString(String username,String password)
    {
        try
        {
            this.username=username;
            Thread.sleep(100000);
            this.password=password;
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

如上述代碼所示,SynchronizedObject 類包含用戶名和密碼兩個成員,printString() 方法用于對這兩個成員進行賦值,但是在設置密碼之前有一個休眠時間。

下面編寫一個線程來對 SynchronizedObject 類進行實例化,并調用 printString() 方法。線程代碼如下:
package ch14;
public class MyThread19 extends Thread
{
    private SynchronizedObject object;
    public MyThread19(SynchronizedObject object)
    {
        super();
        this.object=object;
    }
    @Override
    public void run()
    {
        object.printString("admin","123456");
    }
}

接下來編寫主線程代碼如下:
package ch14;
public class Test23
{
    public static void main(String[] args)
    { 
        try
        { 
            SynchronizedObject object=new SynchronizedObject(); 
            MyThread19 thread=new MyThread19(object); 
            thread.start(); 
            Thread.sleep(500); 
            thread.stop(); 
            System.out.println("用戶名:"+object.getUsername());
            System.out.println("密碼:"+object.getPassword()); 
        }
        catch(InterruptedException e)
        { 
            e.printStackTrace();
        } 
    } 
}

在上述代碼中創建一個 SynchronizedObject 類實例,并將該實例作為參數傳遞給 MyThread19 線程。MyThread19 線程啟動后將立即調用 object.printString('fadminn,"123456") 方法,而在 printString() 方法內有一個較長時間的休眠。該休眠時間大于主線程的休眠時間,所以主線程會繼續往下執行,當執行到 stop() 方法時線程被強制停止。

程序最后的運行結果如下所示。
用戶名:admin
密碼:root

由于 stop() 方法已經在中被標明是“作廢/過期”的方法,顯然它在功能上具有缺陷,所以不建議在程序中使用 stop() 方法。

使用 return 停止線程

除了上面介紹的方法外,還可以將 intermpt() 方法與 return 結合使用來實現停止線程的效果。

例 7

下面通過一個案例來演示這種情況。如下所示為案例中使用 MyThread20 線程類的代碼。
package ch14;
public class MyThread20 extends Thread
{
    @Override
    public void run()
    {
        while (true)
        {
            if (this.isInterrupted())
            {
                System.out.println("停止了!");
                return;
            }
            System.out.println("timer="+System.currentTimeMillis());
        }
    }
}

調用 MyThread20 線程的主線程代碼如下:
package ch14;
public class Test24
{
    public static void main(String[] args) throws InterruptedException
    {
        MyThread20 t=new MyThread20();
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

程序執行后的結果如下所示。
......
timer=1540977194784
timer=1540977194784
timer=1540977194784
timer=1540977194784
timer=1540977194784
停止了!

從程序的執行結果中可以看到成功停止了線程,不過還是建議使用“拋異常”的方法來實現線程的停止,因為在 catch 塊中還可以將異常向上拋,使線程停止的事件得以傳播。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo