Java如何优雅地停止一个线程?
- 如何粗暴的停止一个线程?
- 如何优雅的停止?
- 打招呼的人(设置中断信号)
- 怎么监听中断信号?
- 如果线程休眠了我怎么打断?
- 线程池中线程的打断方式
如何粗暴的停止一个线程?
没有粗暴做对比,就体现不出何为优雅。先说一个大家常用的方式:
kill -9 <pid> //没看错,和进程一样
而与之类似的,在java里有一种粗暴停止线程的方法(当前已经被废弃了,不推荐使用):
thread.stop(); //thread即要停止的线程对象
这种做法会粗暴的停止线程,不管当前线程是否在处理任务,这样极有可能会导致一些未知错误。比如:你正在写一个文件,然后写到一半线程被杀掉了,这个文件就损坏了。
如何优雅的停止?
我们会抱怨说:你想停没有问题,能不能让我把事情处理完你再停,或者你告诉我一声,我打个日志也行啊。这就是粗暴停止的坏处,他压根不通知你。
所以我们自然会想到,如果在停止时候能告诉我一声,我自己决定怎么停止,那该多好。
你既然敢想,java就敢做!于是乎新的API来了!!!
打招呼的人(设置中断信号)
为了满足优雅的停止的要求,java设计了一个方法:
thread.interrupt(); //thread即要停止的线程对象
通过调用此方法,在线程内部会收到一个信号(我们可以理解是打个招呼告诉你有其他人要你停下来),这样我们就可以开始执行停止工作了。
怎么监听中断信号?
监听这个信号很简单,Java提供了两个监听信号的方法:
- 静态方法
boolean interrupted = Thread.interrupted();
- 对象方法
boolean interrupted = Thread.currentThread().isInterrupted();
这两个方法都可以监听到线程是否被打断,但是有一个很大的区别:
- 静态方法在调用后会清除停止信号,即如果你调用第二次,返回的将是false
- 对像方法只是检测是否有停止信息,并不会清除状态,可以被反复调用。
所以,当你想清除停止信号(不想去理会,或者已经处理完了,不想后面再继续处理),可以使用静态方法,否则可以用对象方法。
说了那么多,我们来看个例子:
public class Worker implements Runnable {
@Override
public void run() {
while (true) {
//检测是否有终端信号
if (Thread.interrupted()) {
System.out.println("被打断,break");
break;
}
}
if (Thread.interrupted()) {
//这里不会被执行
System.out.println("被打断,二次检测");
}
}
}
Thread thread = new Thread(new Worker());
thread.start();
thread.interrupt(); //打断
输出:被打断,break
如果线程休眠了我怎么打断?
从上面的例子可以看到,如果线程顺利的运行到了信号检测的位置,我们就可以检测到信号,然后结束线程。但是如果线程处在休眠状态呢?比如:Thread.sleep(10000)
这时候就不得不说thread.interrupt()
第二个功能了,当线程处在休眠状态时,调用thread.interrupt()
会让线程从Thread.sleep(xx)
处抛出InterruptedException
异常,从而打断休眠态,让我们有机会处理这个信号。
举个例子:
public static class Worker implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (Exception e) {
if (e instanceof InterruptedException) {
System.out.println("被打断");
}
}
if (Thread.interrupted()) {
//这里不会被执行
System.out.println("被打断2");
}
}
}
Thread thread = new Thread(new Worker());
thread.start();
thread.interrupt(); //打断
输出:被打断
这里并不会输出“被打断2”,因为在抛出异常时,打断信息已经被清除了。
注意:这里还要提一下:
- 以下方法也支持抛出
InterruptedException
异常打断:wait(), wait(long), wait(long, int), join(), join(long), join(long, int), sleep(long), sleep(long, int)
- 对于实现了
InterruptibleChannel
接口的新版I/O类也支持打断,不过抛出的异常是:java.nio.channels.ClosedByInterruptException
- 对于被
Selector
阻塞的场景,selector会立马返回,并且设置线程的打断状态。
特殊的:对于老的I/O类,比如:Socket,InputStream等是不支持打断的,所以要中断的话,需要从外部close掉,这样会引发内部抛出IOException,需要处理!(这里有一个点,socket在connecting的时候调用close方法无效,暂时没找到什么好办法停止正在连接的socket,有知道的朋友可以评论区说一下,感谢!)
线程池中线程的打断方式
直接看例子:
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<?> submit = executorService.submit(new Worker());
submit.cancel(true); //此处cancel的参数是:mayInterruptIfRunning
- 如果任务已完成、已被取消或由于某些其他原因无法取消,则此尝试将失败。
- 如果成功,并且在调用取消时此任务尚未启动,则此任务不应该运行。
- 如果任务已经开始,则 mayInterruptIfRunning 参数确定是否应该中断执行该任务的线程以尝试停止该任务(调用interrupt()方法)。