Java 正确终止线程的方法

destroy() 为何被废弃

destroy() 只是抛出了一个 NoSuchMethodError,所以该方法无法终止线程:

@Deprecated  
public void destroy() {  
    throw new NoSuchMethodError();  
}  

stop() 为何被废弃

调用 stop() 方法是不安全的,这是因为当调用 stop() 方法时,会发生下面两件事:

  1. 即刻抛出 ThreadDeath 异常,在线程的 run() 方法内,任何一点都有可能抛出 ThreadDeath Error,包括在 catch 或 finally 语句中
  2. 会释放该线程所持有的所有的锁,而这种释放是不可控制的,非预期的
  3. 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止
public class SynchronizedObject {
    private String name = "a";         // 省略 getter、setter
    private String password = "aa";    // 省略 getter、setter

    public synchronized void printString(String name, String password) {
        try {
            this.name = name;
            Thread.sleep(100000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

public class MyThread extends Thread {
    private SynchronizedObject synchronizedObject;
    public MyThread(SynchronizedObject synchronizedObject) {
        this.synchronizedObject = synchronizedObject;
    }

    public void run() {
        synchronizedObject.printString("b", "bb");
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        SynchronizedObject synchronizedObject = new SynchronizedObject();
        Thread thread = new MyThread(synchronizedObject);
        thread.start();
        Thread.sleep(500);
        thread.stop();
        System.out.println(synchronizedObject.getName() + "  " + synchronizedObject.getPassword());
    }
}

输出结果:

b aa

从上面的程序验证结果来看,thread.stop() 会释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop() 后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

Java 中多线程锁释放的条件:

  1. 执行完同步代码块,就会释放锁(synchronized)
  2. 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放(exception)
  3. 在执行同步代码块的过程中,执行了锁所属对象的 wait() 方法,这个线程会释放锁,进入对象的等待池(wait)

从上面的三点就可以看到 stop() 释放锁是在第二点的,通过抛出异常来释放锁,通过证明,这种方式是不安全的,不可靠的。

如何正确地停止一个线程

以下 2 种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是当 run 方法完成后线程终止
  2. 使用 interrupt 方法中断线程

退出标志法

需要 while() 循环在某以特定条件下退出,最直接的办法就是设一个 boolean 标志,并通过设置这个标志来控制循环是否退出:

public class MyThread implements Runnable {
    private volatile boolean isCancelled;
    
    public void run() {
        while (!isCancelled) {
            //do something
        }
    }
    
    public void cancel() { isCancelled=true; }
}

注意,isCancelled 需要为 volatile,保证线程读取时 isCancelled 是最新数据

interrupt

如果线程是阻塞的,则不能使用退出标志法来终止线程。这时就只能使用 Java 提供的中断机制:

  • void interrupt()
    如果线程处于被阻塞状态(例如处于 sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException 异常
    如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true。被设置中断标志的线程将继续正常运行,不受影响

  • static boolean interrupted()
    测试当前线程(正在执行这一命令的线程)是否被中断。这一调用会将当前线程的中断状态重置为 false

  • boolean isInterrupted()
    测试线程是否被终止。不像静态的中断方法,这一调用不改变线程的中断状态

interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就可以这样做:

① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程或者抛出 InterruptedException,使得线程停止的事件得以向上传播

Thread thread = new Thread(() -> {
    while (!Thread.interrupted()) {
        // do more work
    }
    // return or throw InterruptedException
});
thread.start();

// 一段时间以后
thread.interrupt();

thread.interrupted() 清除标志位是为了下次继续检测标志位。如果一个线程被设置中断标志后,选择结束线程那么自然不存在下次的问题,而如果一个线程被设置中断标识后,进行了一些处理后选择继续进行任务,而且这个任务也是需要被中断的,那么当然需要清除标志位了

② 在调用阻塞方法时正确处理 InterruptedException 异常(例如,catch 异常后就结束线程)

public class Interrupt {

    public static void main(String[] args) throws Exception {
        Interrupt t = new Interrupt();
        t.start();
    }

    public void start() {
        MyThread myThread = new MyThread();
        myThread.start();

        try {
            Thread.sleep(3000);
            myThread.cancel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class MyThread extends Thread {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println("test");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("interrupt");
                    // 抛出 InterruptedException 后中断标志被清除,标准做法是再次调用 interrupt 恢复中断
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("stop");
        }

        public void cancel(){
            interrupt();
        }
    }
}

Thread.sleep 这个阻塞方法,接收到中断请求,会抛出 InterruptedException,让上层代码处理。这时,可以什么都不做,但这等于吞掉了中断。因为抛出 InterruptedException 后,中断标记会被重新设置为 false!看 sleep() 的注释,也强调了这点:

@throws InterruptedException
     if any thread has interrupted the current thread. 
     The interrupted status of the current thread is 
     cleared when this exception is thrown.
public static native void sleep(long millis) throws InterruptedException;

记得这个规则:什么时候都不应该吞掉中断!每个线程都应该有合适的方法响应中断!

在接收到中断请求时,标准做法是执行 Thread.currentThread().interrupt() 恢复中断,让线程退出

从另一方面谈起,你不能吞掉中断,也不能中断你不熟悉的线程。如果线程没有响应中断的方法,你无论调用多少次 interrupt() 方法,也像泥牛入海

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355

推荐阅读更多精彩内容