首頁 > 軟體

Java執行緒的停止實現原理詳解

2023-10-31 22:00:11

執行緒停止的原理

使用interrupt來通知,而不是強制

java提供了interrrupt讓一個執行緒來通知另一個執行緒停止

如果想中斷一個執行緒,但是那個執行緒不想去中斷,那就無能為力,我們沒有強制去中斷執行緒的手段,因為執行緒停止前需要做一定的收尾工作

所以正確停止執行緒,是如何用interrupt來通知那個執行緒,以及被停止的執行緒如何進行配合

如何正確停止執行緒

在普通情況下停止執行緒

程式碼展示

  • 呼叫interrupt沒有作用
  • 下面這段程式碼,執行interrupt之後,執行緒並沒有被中斷
  • 因為被執行的執行緒並沒有相應中斷的方式
public class stopThreadWithoutSleep  implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new stopThreadWithoutSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
    @Override
    public void run() {
        int num = 0;
        while(num <= Integer.MAX_VALUE / 2) {
            if (num % 10000 == 0) {
                System.out.println(num + "是10000的倍數");
            }
            num++;
        }
        System.out.println("結束");
    }
}
/*
由於太長,只展示結尾部分的結果 
1073710000是10000的倍數
1073720000是10000的倍數
1073730000是10000的倍數
1073740000是10000的倍數
結束
* */
  • 被執行執行緒加上相應中斷的操作之後
  • 結果可知,被執行執行緒相應一秒之後就結束了
public class stopThreadWithoutSleep  implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new stopThreadWithoutSleep());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
    @Override
    public void run() {
        int num = 0;
        while(!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) {
            if (num % 10000 == 0) {
                System.out.println(num + "是10000的倍數");
            }
            num++;
        }
        System.out.println("結束");
    }
}
/*
由於太長,只展示結尾部分的結果
587830000是10000的倍數
587840000是10000的倍數
587850000是10000的倍數
587860000是10000的倍數
結束
* */

在阻塞情況下停止執行緒

程式碼展示

  • 中斷之後,丟擲異常
  • 執行緒在sleep的過程中,會catch到InterruptedException這個異常,從而相應中斷
public class stopThreadWithSleep {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數");
                }
                num++;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
/*
* 0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.stopThreadWithSleep.lambda$main$0(stopThreadWithSleep.java:15)
   at java.lang.Thread.run(Thread.java:748)
* */

執行緒在每次迭代後都阻塞

  • 程式碼展示 即使不在while判斷是否中斷,sleep也能中斷異常
public class stopThreadWithSleepEveryLoop {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 10000) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍數");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (
                InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
/*
* 0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.stopThreadWithSleepEveryLoop.lambda$main$0(stopThreadWithSleepEveryLoop.java:15)
   at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
* 
* */

當catch寫到while內,則不能正常中斷

public class CantInterrupt {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數");
                }
                num ++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (
                InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
/*
* 0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.CantInterrupt.lambda$main$0(CantInterrupt.java:14)
   at java.lang.Thread.run(Thread.java:748)
400是100的倍數
500是100的倍數
600是100的倍數
700是100的倍數
800是100的倍數
900是100的倍數
Process finished with exit code -1
* */
  • 即使在while的判斷條件中,加上檢測中斷的機制,也不能正常中斷
  • 因為java的sleep函數,一旦相應中斷,就會將中斷的標誌位刪除
public class CantInterrupt {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (
                InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
/*
0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.CantInterrupt.lambda$main$0(CantInterrupt.java:14)
   at java.lang.Thread.run(Thread.java:748)
400是100的倍數
500是100的倍數
Process finished with exit code -1
* */

停止執行緒的最佳實踐

  • 在方法簽名中丟擲異常,在run方法中強制進行try catch
public class StopThreadInProd implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThreadInProd());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
    @Override
    public void run() {
        while (true) {
            System.out.println("start");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                System.out.println("儲存紀錄檔/關閉程式");
                e.printStackTrace();
            }
        }
    }
    private void throwInMethod() throws InterruptedException {
            Thread.sleep(2000);
    }
}
/*
* start
儲存紀錄檔/關閉程式
start
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.StopThreadInProd.throwInMethod(StopThreadInProd.java:26)
   at com.jx.JavaTest.stopThread.StopThreadInProd.run(StopThreadInProd.java:17)
   at java.lang.Thread.run(Thread.java:748)
start
start
Process finished with exit code -1
* 
* */
  • 在catch語句中呼叫Thread.currentThread().interrupt恢復中斷狀態
  • 結果:丟擲異常,程式結束
public class StopThreadInProd2 implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupt");
                break;
            }
            reInterrupt();
        }
    }
    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}
/*
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.StopThreadInProd2.reInterrupt(StopThreadInProd2.java:25)
   at com.jx.JavaTest.stopThread.StopThreadInProd2.run(StopThreadInProd2.java:19)
   at java.lang.Thread.run(Thread.java:748)
Interrupt
*
* */
  • 依照上面的程式碼,如果方法沒有沒有重新丟擲異常
  • 結果:程式丟擲異常,但是程式沒有停止執行
public class StopThreadInProd2 implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupt");
                break;
            }
            reInterrupt();
        }
    }
    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
//            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}
/*
java.lang.InterruptedException: sleep interrupted
   at java.lang.Thread.sleep(Native Method)
   at com.jx.JavaTest.stopThread.StopThreadInProd2.reInterrupt(StopThreadInProd2.java:25)
   at com.jx.JavaTest.stopThread.StopThreadInProd2.run(StopThreadInProd2.java:19)
   at java.lang.Thread.run(Thread.java:748)
*
* */

錯誤停止的方法

被棄用的stop,suspend和resume方法

  • 使用stop停止執行緒,會導致執行緒執行一半突然停止,沒辦法完成最基本的操作,會造成髒資料
  • 下面這段程式碼的結果會造成一個連隊只有部分人領取到了裝備
  • stop是不安全的,會直接停止監視器
  • suspend和resume不會破壞物件,但是會讓執行緒掛起,不釋放鎖,容易造成死鎖
public class StopThread implements Runnable{
    @Override
    public void run() {
        // 模擬指揮軍隊,一共五個連隊,每個連隊一百人
        // 以連隊為單位發放武器
        for (int i = 0; i < 5; i++) {
            System.out.println("連隊" + i + "領取武器");
            for (int j = 0; j < 10; j++) {
                System.out.println(j);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("連隊" + i + "領取完畢");
        }
    }
    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();
    }
}
/*
* 連隊0領取武器
0
1
2
3
4
5
6
7
8
9
連隊0領取完畢
連隊1領取武器
0
1
2
3
4
5

Process finished with exit code 0

* */

用volatile設定boolean標記位

  • 下面這段程式碼,通過改變標誌位的值會成功終止執行緒
public class Volatile implements Runnable {
    private volatile boolean canceled = false;
    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 10000 && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + " 是100的倍數");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Volatile v = new Volatile();
        Thread thread = new Thread(v);
        thread.start();
        Thread.sleep(1000);
        v.canceled = true;
    }
}
/*
*0 是100的倍數
100 是100的倍數
200 是100的倍數
300 是100的倍數
400 是100的倍數
500 是100的倍數
600 是100的倍數
Process finished with exit code 0
* 
* */
  • 當陷入阻塞的時候,是無法停止執行緒的
  • 下面這段程式碼的執行結果,並沒有列印生產者停止執行,說明根本沒有執行生產者的finally那部分程式碼
  • 同時程式也沒停止
  • 原因見生產者程式碼 while迴圈中的註釋
// 模擬生產者和消費者
public class cantStop {
    public static void main(String[] args) throws InterruptedException {
        // 阻塞佇列
        // 滿了之後,放不進去
        // 空的時候取資料,也會堵塞
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);
        Consumer consumer = new Consumer(storage);
        while (consumer.needMore()) {
            System.out.println(consumer.storage.take() + "被消費");
            Thread.sleep(100);
        }
        System.out.println("消費者不需要更多資料");
        // 消費者不需要資料,讓生產者停下來
        producer.canceled = true;
    }
}
// 生產者
class Producer implements Runnable {
    public volatile boolean canceled = false;
    BlockingQueue storage;
    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }
    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 10000 && !canceled) {
                if (num % 100 == 0) {
                    // 當堵塞佇列滿了之後,會堵塞在這裡,而這段程式碼沒有判斷機制
                    storage.put(num);
                    System.out.println("num" + "生產");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生產者停止執行");
        }
    }
}
// 消費者
class Consumer {
    BlockingQueue storage;
    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }
    public boolean needMore() {
        if (Math.random() > 0.9) {
            return false;
        }
        return true;
    }
}
/*
* num生產
num生產
num生產
num生產
num生產
num生產
num生產
num生產
num生產
num生產
0被消費
num生產
消費者不需要更多資料

* 
* */
  • 將上面程式碼用interrupt進行中斷
  • 程式成功停止
public class finxed {
    public static void main(String[] args) throws InterruptedException {
        finxed finxed = new finxed();
        // 阻塞佇列
        // 滿了之後,放不進去
        // 空的時候取資料,也會堵塞
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = finxed.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);
        Consumer consumer = finxed.new Consumer(storage);
        while (consumer.needMore()) {
            System.out.println(consumer.storage.take() + "被消費");
            Thread.sleep(100);
        }
        System.out.println("消費者不需要更多資料");
        // 消費者不需要資料,讓生產者停下來
        producerThread.interrupt();
    }
    class Producer implements Runnable {
        public volatile boolean canceled = false;
        BlockingQueue storage;
        public Producer(BlockingQueue storage) {
            this.storage = storage;
        }
        @Override
        public void run() {
            int num = 0;
            try {
                while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        storage.put(num);
                        System.out.println("num" + "生產");
                    }
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生產者停止執行");
            }
        }
    }
    class Consumer {
        BlockingQueue storage;
        public Consumer(BlockingQueue storage) {
            this.storage = storage;
        }
        public boolean needMore() {
            if (Math.random() > 0.9) {
                return false;
            }
            return true;
        }
    }
}
/*
* 2100被消費
num生產
2200被消費
num生產
2300被消費
num生產
消費者不需要更多資料
生產者停止執行
java.lang.InterruptedException
   at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
   at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
   at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
   at com.jx.JavaTest.stopThread.volatiledmo.finxed$Producer.run(finxed.java:51)
   at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
* 
* */

interrupt原始碼檢視

  • 這段程式碼做的都是一些判斷,真正執行中斷的程式碼時interrupt0
  • interrupt0是native程式碼
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}
private native void interrupt0();

interrupt相關函數練習

  • isInterrupted獲取中斷標誌,獲取的是前面的執行緒
  • interrupted獲取中斷標誌並重置,只關心執行的執行緒,所以下面程式碼執行的是main執行緒
public class InterruptedTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {

                }
            }
        });
        thread.start();
        thread.interrupt();
        // 獲取中斷標誌
        System.out.println(thread.isInterrupted()); // true
        // 獲取中斷標誌並重置
        System.out.println(thread.interrupted()); //false
        System.out.println(Thread.interrupted()); // false
        System.out.println(thread.isInterrupted()); //true
        thread.join();
        System.out.println("over");
    }
}

到此這篇關於Java執行緒的停止實現原理詳解的文章就介紹到這了,更多相關Java執行緒的停止內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com