在线程运行的过程中 如何正确的停止线程? 是否可以使用volatile来停止线程?
使用Interrupt
对于java而言 最正确的停止线程的方式就是Interrupt,但是Interrupt仅仅起到了通知线程的作用 并不会主动的去中断线程
看一下使用方式
while (!Thread.currentThread().isInterrupted() && more work to do) {
do more work
}
当线程调用interrupt方法之后 这个线程的中断标记位就被置为true 我们可以通过isInterrupted()
方法来检测是否中断 并且做出响应
sleep期间是否可以响应到中断
我们在使用中断的过程中 需要思考另一个问题 就是如果我们的线程sleep或者wait了 是否还可以响应中断? 如果不能响应中断 那么响应延后性就太强了
看一下代码
Runnable runnable = () -> {
int num = 0;
try {
while (!Thread.currentThread().isInterrupted() &&
num <= 1000) {
System.out.println(num);
num++;
Thread.sleep(1000000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
看一下sleep时是否可以响应interrupt的代码
Runnable runnable = new Runnable() {
@Override
public void run() {
int i = 0;
while (!Thread.currentThread().isInterrupted() && i < 1000) {
i++;
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程中断");
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
try {
Thread.sleep(5000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
我们发现是可以响应到Interrupt中断的 当我们调用interrupt时 线程是可以响应到中断信号的 并且会抛出InterruptedException异常 并且重新将中断信号置为false java在设计之初就考虑到了这种情况 我们应该在catch中对异常做处理或者抛出异常
如果我们在catch中不做任何处理 相当于把中断隐藏了 这是非常不合理的
使用volatile来中断线程
我们可以使用标志位来处理 每次循环都判断一下标志位是否为false 如果为false 则中断线程
public class VolatileCanStop implements Runnable {
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (!canceled && num <= 1000000) {
if (num % 10 == 0) {
System.out.println(num + "是10的倍数。");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
VolatileCanStop r = new VolatileCanStop();
Thread thread = new Thread(r);
thread.start();
Thread.sleep(3000);
r.canceled = true;
}
}
但是使用volatile使用过程中 会有一个弊端
比如在线程运行过程中 处于阻塞状态 这时候我们将canceled标志位改了发现并不生效 因为线程目前已经是阻塞状态了 需要先变成运行状态 才可以响应到canceled标志位
以生产者/消费者为例
class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 50 == 0) {
storage.put(num);
System.out.println(num + "是50的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
我们看到 当仓库满的时候 BlockingQueue会阻塞线程 这时候我们已经没办法响应canceled了 相反 我们可以响应interrupt信号
总结
总的来看 中断线程的方法最合适的只有interrupt方法 或者某些场景下 我们可以使用volatile关键字来实现线程中断