如何正确结束Java线程

线程的启动很简单,但用户可能随时取消任务,怎么样让跑起来的线程正确地结束,这是今天要讨论的话题。

使用标志位

很简单地设置一个标志位,名称就叫做isCancelled。启动线程后,定期检查这个标志位。如果isCancelled=true,那么线程就马上结束。

public class MyThread implements Runnable{
    private volatile boolean isCancelled;

    public void run(){
        while(!isCancelled){
            //do something
        }
    }

    public void cancel(){   isCancelled=true;    }
}

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

我以前经常用这种简单方法,在大多时候也很有效,但并不完善。考虑下,如果线程执行的方法被阻塞,那么如何执行isCancelled的检查呢?线程有可能永远不会去检查标志位,也就卡住了。

使用中断

Java提供了中断机制,Thread类下有三个重要方法。

  • public void interrupt()
  • public boolean isInterrupted()
  • public static boolean interrupted(); // 清除中断标志,并返回原状态

每个线程都有个boolean类型的中断状态。当使用Thread的interrupt()方法时,线程的中断状态会被设置为true。

下面的例子启动了一个线程,循环执行打印一些信息。使用isInterrupted()方法判断线程是否被中断,如果是就结束线程。

public class InterruptedExample {

    public static void main(String[] args) throws Exception {
        InterruptedExample interruptedExample = new InterruptedExample();
        interruptedExample.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();
        }
    }
}

对线程调用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;

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

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

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

用Java库的方法比自己写的要好

自己手动调用interrupt()方法来中断程序,OK。但是Java库提供了一些类来实现中断,更好更强大。

Executor框架提供了Java线程池的能力,ExecutorService扩展了Executor,提供了管理线程生命周期的关键能力。其中,ExecutorService.submit返回了Future对象来描述一个线程任务,它有一个cancel()方法。

下面的例子扩展了上面的InterruptedExample,要求线程在限定时间内得到结果,否则触发超时停止。

public class InterruptByFuture {

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newSingleThreadExecutor();
        Future<?> task = es.submit(new MyThread());

        try {
            //限定时间获取结果
            task.get(5, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            //超时触发线程中止
            System.out.println("thread over time");
        } catch (ExecutionException e) {
            throw e;
        } finally {
            boolean mayInterruptIfRunning = true;
            task.cancel(mayInterruptIfRunning);
        }
    }

    private static class MyThread extends Thread {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {   
                try {
                    System.out.println("count");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("interrupt");
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("thread stop");
        }

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

Future的get方法可以传入时间,如果限定时间内没有得到结果,将会抛出TimeoutException。此时,可以调用Future的cancel()方法,对任务所在线程发出中断请求。

cancel()有个参数mayInterruptIfRunning,表示任务是否能够接收到中断。
- mayInterruptIfRunning=true时,任务如果在某个线程中运行,那么这个线程能够被中断;
- mayInterruptIfRunning=false时,任务如果还未启动,就不要运行它,应用于不处理中断的任务

要注意,mayInterruptIfRunning=true表示线程能接收中断,但线程是否实现了中断不得而知。线程要正确响应中断,才能真正被cancel。

线程池的shutdownNow()会尝试停止池内所有在执行的线程,原理也是发出中断请求。对于线程池的停止,下次新开一篇再讲吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值