如何在Java中正确的终止一个线程
2017-04-28 编程语言 java thread 线程 838 字 2 分钟
含有死循环的线程是无法知道自己该什么时候结束的
一个工作线程需要不断接受外部的消息,这样的线程内部通常有一个死循环 while(true){}
。因为死循环的存在,该线程自己无法知道何时才能停止循环,只能通过外部线程通知该线程的结束。
要结束一个线程,Thread里有一个 stop()
方法可以粗暴的杀死一个线程。这样做会出现一些问题,被结束的线程会被立即停止,没有时间处理一些善后工作。在多线程环境下,就可能会出现数据不一致的问题,这是非常危险的。因此,stop()
方法被置为_弃用_方法。
那么如何才能更好的结束一个线程?
这里有两种方法可以让外部线程结束一个线程
我们需要给线程处理善后工作的机会,一般来说就是给线程一个信号,希望线程能尽快完善工作,然后跳出死循环。
设置标记变量
在线程工作之余,检查标记变量,如果标记变量被改变,则自行中断退出。
public class Thread1 extends Thread {
private volatile boolean isStop = false;
public void stopMe() {
isStop = true;
}
@Override
public void run() {
while(!isStop){
// 处理逻辑
}
}
}
这里增加一个volatile
标注的变量isStop来记录是否需要结束线程,run()
中的死循环也由isStop变量操控。通过调用stopMe()方法改变变量的值,使死循环可以自行退出。
捕获线程中断
关于线程中断有Thread中有几个方法:
public void Thread.interrupt() // 中断一个线程
public boolean Thread.isInterrupted() // 判断线程是否中断
public static boolean Thread.interrupted() // 判断是否中断,并清除中断位
通过Thread.interrupt()
方法可以设置一个线程的中断位,线程通过检查中断位来自行进行中断操作。
public class Thread2 extends Thread {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
// 处理逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 设置中断状态
}
}
}
}
需要注意的是一些阻塞方法像sleep()
等会清除中断标志,所以在catch的时候需要重新设置一下中断标志。
太忙的线程无法及时听到撤退的号角
如果线程阻塞在一个事件上,那么给线程发送关闭消息,线程也无法及时的收到,只能等到事件出现,线程退出阻塞状态的时候才能接着进行下一步操作。
对于这种情况,起初也没有好的方法,于是就假想了一下:可以在通知线程关闭的时候,发送一个_假_的事件,让线程强行退出阻塞状态。但是这种使用假事件的方式很容易造成系统“混乱”,不是一个很好的方法。
如果一个线程阻塞在Socket通信上,可以在另一个线程上调用这个Socket的close()
方法,这样会使其抛出SocketException
而退出。官方文档没有提到这种方法,可以用来做个参考。
一个比较好的思路就是,改阻塞为非阻塞,这样线程就有足够的机会捕获到关闭消息。比如用NIO包中的Channel来管理Socket。
以ZMQ为例,通过ZMQ.Poller
轮询避免阻塞的发生。以下代码是官方的示例:
import org.zeromq.ZMQ;
public class MSPoller {
public static void main (String[] args) {
ZMQ.Context context = ZMQ.context(1);
// Connect to task ventilator
ZMQ.Socket receiver = context.socket(ZMQ.PULL);
receiver.connect("tcp://localhost:5557");
// Connect to weather server
ZMQ.Socket subscriber = context.socket(ZMQ.SUB);
subscriber.connect("tcp://localhost:5556");
subscriber.subscribe("10001 ".getBytes());
// Initialize poll set
ZMQ.Poller items = new ZMQ.Poller (2);
items.register(receiver, ZMQ.Poller.POLLIN);
items.register(subscriber, ZMQ.Poller.POLLIN);
// Process messages from both sockets
while (!Thread.currentThread ().isInterrupted ()) {
byte[] message;
items.poll();
if (items.pollin(0)) {
message = receiver.recv(0);
System.out.println("Process task");
}
if (items.pollin(1)) {
message = subscriber.recv(0);
System.out.println("Process weather update");
}
}
receiver.close ();
context.term ();
}
}
总的来说,尽量通知线程自己去结束自己的任务,由此可以尽量避免一些意外情况的发生。
参考内容