【探索】停止線程和暫停線程

停止線程和暫停線程

停止一個線程意味着在線程處理完任務之前停止正在做的操作,也就是放棄當前的操作,雖然看起來非常簡單,但是要做好防範措施。

停止線程的方法

1. 方法1:使用Thread.stop(),但不推薦

2. 方法2:使用Thread.interrupt()

2.1 使用實例:

創建文件MyThread.java:

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0;i < 50000 ; i++){
            System.out.println("i="+i);
        }
    }
}

創建測試類RunTest.java:


public class RunTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
        System.out.println("ZZZZZZZZZ");
    }
}

預期結果:輸出50000行+ZZZZZ,實際輸出50001行+zzzz

這說明了什麼?調用interrupt()方法並沒有真正的把線程停止。

2.2 判斷線程是否處於停止狀態

2.2.1 public static boolean interrupted():測試currentThread()是否已經中斷,執行後具有清除狀態標誌值爲false的功能,如:第一次調用時,當前線程未中斷,清除該標誌在第二次調用檢驗中斷狀態之前,第二次的調用結果就爲了true。

2.2.2 public boolean this.isInterrupted():測試this關鍵字所在類的對象是否已經中斷,不清楚狀態標誌。

3 .判斷線程是否處於停止狀態即可判斷後面的代碼是否可運行,如果線程處於停止狀態,則後面的代碼不再運行。

3.1 能停止的線程------>異常法,如果結果不明顯,可配合斷點一起使用便於觀察結果

修改MyThread.java如下:

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0;i < 50000 ; i++){
            if(this.isInterrupted()){
                System.out.println("我已經是停止狀態了,我要退出了");
                break;
            }
            System.out.println("i="+i);
        }
    }
}

修改RunTest.java如下:

public class RunTest {
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(5000);
            thread.interrupt();
        }catch (InterruptedException e){
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end!");
    }
}

實際的運行結果:

i=0
i=1
end!
我已經是停止狀態了,我要退出了
3.2 在3.1中的例子雖然停止了線程,但如果for語句下面還有語句,那麼程序依然會繼續運行,如圖:

類MyThread修改如下:

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0;i < 50000 ; i++){
            if(this.isInterrupted()){
                System.out.println("我已經是停止狀態了,我要退出了");
                break;
            }
            System.out.println("i="+i);
        }
        System.out.println("我被輸出了,是for下的語句,線程結束後我還能執行嘿!");
    }
}

實際運行結果如下,說明並沒有阻止for後的語句再繼續執行

i=0
i=1
i=2
end!
我已經是停止狀態了,我要退出了
我被輸出了,是for下的語句,線程結束後我還能執行嘿!
3.2.1 解決辦法,即使用interrupt()正確停止線程的方法

修改MyThread類如下:

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0;i < 50000 ; i++){
                if(this.isInterrupted()){
                    System.out.println("我已經是停止狀態了,我要退出了");
                    throw  new InterruptedException();
                }
                System.out.println("i="+i);
            }
            System.out.println("我被輸出了,是for下的語句!");
        }catch (InterruptedException e){
            System.out.println("進入了MyThread類的run方法中的catch了");
            e.printStackTrace();
        }
    }
}

實際的運行結果部分如下:

i=14006
i=14007
i=14008
i=14009
我已經是停止狀態了,我要退出了
end!
進入了MyThread類的run方法中的catch了
java.lang.InterruptedException
	at MyThread.run(MyThread.java:10)

4.使用“return;”語句和interrupt()方法結合使用停止線程的缺點和解決方案

4.1 修改MyThread類如下:
public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        while(true){
            if(this.isInterrupted()){
                System.out.println("停止了哦!");
                return;
            }
            System.out.println("timer:"+System.currentTimeMillis());
        }
    }
}

4.2 修改RunTest類如下:
public class RunTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

最後的實際運行結果爲,證明停止了之後,沒有再繼續往下執行

timer:1592300686447
timer:1592300686447
timer:1592300686447
timer:1592300686447
停止了哦!
4.3 使用return的缺點是:不能像拋異常法那樣對異常的信息進行統一的處理,解決辦法:只能在每個return前都要搭配一個寫入日誌的操作,所以這會使得代碼冗餘,所以還是建議使用拋異常的方法來停止線程。

5. 暫停線程

在Java多線程中,可以使用suspend()來暫停線程,resume()來恢復線程的執行。

簡單使用的示例:

public class MyThread extends Thread{
    private long i = 0;
    public  long getI(){
        return  i;
    }
    public void  setI(long i){
        this.i = i;
    }
    @Override
    public void run() {
        super.run();
        while(true){
            i++;
        }
    }
}

我的RunTest類如下:

public class RunTest {
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(5000);
            //A段
            thread.suspend();//此方法已過期,但是研究過期方法爲什麼被丟棄也是件有趣的事情
            System.out.println("A="+System.currentTimeMillis()+",i="+thread.getI());
            Thread.sleep(5000);
            System.out.println("A="+System.currentTimeMillis()+",i="+thread.getI());
            //B段
            thread.resume();
            Thread.sleep(5000);
            //C段
            thread.suspend();
            System.out.println("B="+System.currentTimeMillis()+",i="+thread.getI());
            Thread.sleep(5000);
            System.out.println("B="+System.currentTimeMillis()+",i="+thread.getI());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

最後的運行結果爲:

A=1592302312763,i=2823139737
A=1592302317763,i=2823139737
B=1592302322763,i=5736528459 ----》此時線程已被暫停,不再執行run方法的i++
B=1592302327763,i=5736528459 ----->已被暫停的線程,未被恢復,所以i值不變的

5.1 suspend()方法與resume()方法的缺點——————獨佔資源

如果suspend()方法與resume()方法使用不當,極易造成公共同步對象被佔,其他線程無法訪問公共對象的結果。

5.1.1 創建模擬公共對象類:
public class SynchronizedObject {
    synchronized public void printString(){
        System.out.println("begin");
        if(Thread.currentThread().getName().equals("a")){
            System.out.println("a線程永遠被暫停了");
            Thread.currentThread().suspend();
        }
        System.out.println("end!");
    }
}
5.1.2 創建RunTest類:
public class RunTest {
    public static void main(String[] args) {
       try {
           final SynchronizedObject obj = new SynchronizedObject();
           Thread thread1 = new Thread(){
               @Override
               public void run() {
                   obj.printString();
               }
           };
           thread1.setName("a");
           thread1.start();
           Thread.sleep(1000);
           Thread thread2 = new Thread(){
               @Override
               public void run() {
                   System.out.println("Thread2啓動了,但卻進入不了printString,無法打印出b的begin");
                   obj.printString();
               }
           };
           thread2.setName("b");
           thread2.start();
       }catch (InterruptedException e){
           e.printStackTrace();
       }
    }
}

實際運行結果,A線程獨佔並鎖死了公共資源printString()方法

begin
a線程永遠被暫停了
Thread2啓動了,但卻進入不了printString,無法打印出b的begin

5.2 另一種獨佔資源鎖死的情況

5.2.1 創建MyThread類:
public class MyThread extends Thread{
    private long i = 0;
    @Override
    public void run() {
        super.run();
        while(true){
            i++;
        }
    }
}
5.2.2 創建RunTest類:
public class RunTest {
    public static void main(String[] args) {
       try {
           MyThread thread = new MyThread();
           thread.start();
           Thread.sleep(1000);
           thread.suspend();
           System.out.println("mainEnd!");
       }catch (InterruptedException e){
           e.printStackTrace();
       }
    }
}

運行結果如下:輸出mainEnd,但是可以看到進程未被銷燬,在控制檯看到是呈紅色按鈕。

但是如果將MyThread.java更改成如下:

public class MyThread extends Thread{
    private long i = 0;
    @Override
    public void run() {
        super.run();
        while(true){
            i++;
            System.out.println(i);
        }
    }
}

運行結果如下:未輸出mainEnd

144067
144068
144069
144070
144071
144072
144073
144074

產生這種情況的原因是:當程序運行到System.out.prtintln(i)內部時,同步鎖是不釋放的,println()的源代碼如下:

 public  void  println(long x){
        synchronized (this){
            print(x);
            newLine();
        }
    }

以上便是多線程的暫停和徹底停止的內容,關於suspend()和resume()一起使用還可能造成數據的不完整的缺點這裏就不再述說,現在JDK版本已經將這兩個方法作廢了,想要對線程進行暫停與恢復的處理,可使用wait(),notify()或notifyAll()方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章