Java錯(cuò)誤的停止線程的方法

1. 被棄用的 stop、suspend 和 resume 方法

stop() 來(lái)停止線程,會(huì)導(dǎo)致線程運(yùn)行一半突然停止,沒(méi)辦法完成一個(gè)基本單位的操作,會(huì)造成臟數(shù)據(jù);
模擬連隊(duì)發(fā)裝備代碼示例:

public class StopThread implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("連隊(duì)" + i + " 開(kāi)始領(lǐng)取:");
            for (int j = 1; j <= 10; j++) {
                System.out.println("士兵" + j + " 開(kāi)始領(lǐng)取");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("====連隊(duì)" + i + "領(lǐng)取完畢====");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        // 等待666毫秒,停止線程
        Thread.sleep(666);
        thread.stop();
    }
}

運(yùn)行結(jié)果:

連隊(duì)1 開(kāi)始領(lǐng)?。?士兵1 開(kāi)始領(lǐng)取
士兵2 開(kāi)始領(lǐng)取
士兵3 開(kāi)始領(lǐng)取
士兵4 開(kāi)始領(lǐng)取
士兵5 開(kāi)始領(lǐng)取
士兵6 開(kāi)始領(lǐng)取
士兵7 開(kāi)始領(lǐng)取
士兵8 開(kāi)始領(lǐng)取
士兵9 開(kāi)始領(lǐng)取
士兵10 開(kāi)始領(lǐng)取
====連隊(duì)1領(lǐng)取完畢====
連隊(duì)2 開(kāi)始領(lǐng)?。?士兵1 開(kāi)始領(lǐng)取
士兵2 開(kāi)始領(lǐng)取
士兵3 開(kāi)始領(lǐng)取

Process finished with exit code 0

會(huì)發(fā)現(xiàn) 連隊(duì)2 才3個(gè)人領(lǐng)完,其他人都還沒(méi)有領(lǐng)到,線程突然就結(jié)束了,這樣就會(huì)造成數(shù)據(jù)的錯(cuò)亂!
并且這種數(shù)據(jù)的錯(cuò)亂后期難以排查!
Oracle官方文檔對(duì)于為什么Thread.stop不推薦使用的解釋
https://docs.oracle.com/javase/7/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

image.png

suspend和resume被拋棄原因:
suspend和stop不一樣,suspend不會(huì)破壞對(duì)象,但是它會(huì)讓一個(gè)線程掛起,在恢復(fù)之前,鎖不會(huì)釋放,也就是它是帶著鎖去進(jìn)行休息的,這樣的話很容易造成死鎖。
resume

2. 用volatile設(shè)置boolean標(biāo)記位

2.1:看似可行的代碼

public class WrongWayVolatile implements Runnable {

    /**
     * 設(shè)置boolean類型的標(biāo)記位
     */
    private volatile boolean canceled = false;

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread t = new Thread(r);
        t.start();

        // 等待5秒后
        Thread.sleep(5000);
        // 更改canceled值以達(dá)到停止程序的目的
        r.canceled = true;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= Integer.MAX_VALUE && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍數(shù)");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序在運(yùn)行5秒鐘后停止了,達(dá)到了預(yù)期的效果。

2.2:當(dāng)陷入阻塞時(shí),無(wú)法停止
下面代碼示例中,生產(chǎn)者的生產(chǎn)速度很快,消費(fèi)者消費(fèi)速度慢,
所以阻塞隊(duì)列滿了以后,生產(chǎn)者會(huì)阻塞停止生產(chǎn),等待消費(fèi)者進(jìn)一步消費(fèi)

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class WrongWayVolatileCantStop {

    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();
        // 等待1秒讓生產(chǎn)者將隊(duì)列填滿
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消費(fèi)了!");
            // 消費(fèi)是需要時(shí)間的,設(shè)置個(gè)100毫秒
            Thread.sleep(100);
        }
        System.out.println("消費(fèi)者不需要更多數(shù)據(jù)了。");

        // 一旦消費(fèi)者不需要更多數(shù)據(jù)了,則應(yīng)該讓生產(chǎn)者停下來(lái),
        // 將標(biāo)記位設(shè)置為true,看是否能將線程停止?
        producer.canceled = true;
        System.out.println(producer.canceled);
    }

}

/**
 * 生產(chǎn)者
 */
class Producer implements Runnable {

    /**
     * 設(shè)置boolean類型的標(biāo)記位
     */
    public volatile boolean canceled = false;

    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= Integer.MAX_VALUE && !canceled) {
                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍數(shù),被放到了倉(cāng)庫(kù)中。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生產(chǎn)者結(jié)束運(yùn)行");
        }
    }
}

/**
 * 消費(fèi)者
 */
class Consumer {
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        // 隨機(jī)返回true或false
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}

最后的運(yùn)行結(jié)果并沒(méi)有打印出 “生產(chǎn)者結(jié)束運(yùn)行”,
而且程序也沒(méi)有停止!??!

程序沒(méi)有停止

為什么?
因?yàn)樯a(chǎn)者暫停生產(chǎn)時(shí),是阻塞在 storage.put(num);
而且也沒(méi)有人去喚醒,所以 while() 條件也無(wú)法執(zhí)行,也不知道 canceled 的值已經(jīng)被改變!
只會(huì)一直等在 storage.put(num); 這里。
解決方法:使用 interrupt

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class WrongWayVolatileCantStop {

    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();
        // 等待1秒讓生產(chǎn)者將隊(duì)列填滿
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消費(fèi)了!");
            // 消費(fèi)是需要時(shí)間的,設(shè)置個(gè)100毫秒
            Thread.sleep(100);
        }
        System.out.println("消費(fèi)者不需要更多數(shù)據(jù)了。");

        // 一旦消費(fèi)者不需要更多數(shù)據(jù)了,則應(yīng)該讓生產(chǎn)者停下來(lái),
        // 使用interrupt通知停止線程
        producerThread.interrupt();
    }

}

/**
 * 生產(chǎn)者
 */
class Producer implements Runnable {
    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍數(shù),被放到了倉(cāng)庫(kù)中。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生產(chǎn)者結(jié)束運(yùn)行");
        }
    }
}

/**
 * 消費(fèi)者
 */
class Consumer {
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        // 隨機(jī)返回true或false
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容