文章目录
思维导图链接
参考慕课网Java并发核心知识体系精讲课程
线程的中断 总览
常见面试题:
1. 如何停止线程
解答:
-
一、使用interrupt方法请求,而不是强制终止线程,是合作机制。
这样,被请求中断的线程可以自主决定,处理自己的逻辑。
这样做的好处是,可以保证数据安全,来得及清理,能够保证数据完整性。
-
二、若让interrupt方法起效,需要多方面的配合使用
-
是请求方发出中断请求
-
是被停止方要在每次循环中或适当的时候检查中断信号,
并在可能抛出InterruptException的时候处理这个信号
-
是如果线程停止的是子方法,子方法被调用,有两种最佳实践
1)传递中断、即优先在子方法层向上抛出异常,将中断信号传给run方法,在run方法层处理中断信号逻辑
2)恢复中断、即子方法收到中断信号后,再次设置中断状态。
-
-
三、 如果不用interrupt,其他方法会有一定的弊端与后果
1)stop会突然停止线程,线程来不及处理剩下的数据,会导致数据不完整
2)suspend等方法会使线程挂起,不会破坏对象,抱着锁阻塞,会导致死锁
3)用volatile设置boolean标记位无法处理长时间阻塞的情况,导致线程无法停止
相关代码证明
子方法中断两种最佳实践:
- 传递中断:
package threadcoreknowledge.stopthreads;
import threadcoreknowledge.createthreads.ThreadStyle;
/**
* 描述: 最佳实践1:
* catch了InterruptedExcetion之后的优先选择:
* 在方法签名中抛出异常 那么在run()就会强制try/catch
*/
public class RightWayStopThreadInProd implements Runnable {
@Override
public void run() {
while (true && !Thread.currentThread().isInterrupted()) {
System.out.println("go");
try {
throwInMethod();
} catch (InterruptedException e) { // run方法处理异常逻辑
Thread.currentThread().interrupt();
//保存日志、停止程序
System.out.println("保存日志");
e.printStackTrace();
}
}
}
private void throwInMethod() throws InterruptedException { // 子过程向上抛异常
Thread.sleep(2000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
- 恢复中断:
package threadcoreknowledge.stopthreads;
/**
* 描述:最佳实践2:
* 在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,
* 以便于在后续的执行中,依然能够检查到刚才发生了中断
* 回到刚才RightWayStopThreadInProd补上中断,让它跳出
*/
public class RightWayStopThreadInProd2 implements Runnable {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) { // 检测中断
System.out.println("Interrupted,程序运行结束");
break;
}
reInterrupt();
}
}
private void reInterrupt() {
try {
Thread.sleep(2000); // 中断标志清空,变成false
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新中断,标志位设置true
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
用volatile设置boolean标记位不能处理长时间阻塞的情况
package threadcoreknowledge.stopthreads.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* 描述: 演示用volatile的局限part2 陷入阻塞时,volatile是无法停止线程的
* 此例中,生产者的生产速度很快,消费者消费速度慢,
* 所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
*/
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
// 定义阻塞队列
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
// 生产者启动
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
// 消费者启动
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()) { // 消费者正常消费
System.out.println(consumer.storage.take()+"被消费了");
Thread.sleep(100);
}
// 消费者停止消费
System.out.println("消费者不需要更多数据了。");
//一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况不起作用
producer.canceled=true;
System.out.println(producer.canceled); // true
}
}
// 生产者逻辑
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 % 100 == 0) {
storage.put(num); // 阻塞队列添加元素,被中断会响应异常
System.out.println(num + "是100的倍数,被放到仓库中了。");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者结束运行");
}
}
}
// 消费者逻辑
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
// 设置随机一定的比例让消费者不消费
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
- 修复方案
...
// 用interrupt来中断
producerThread.interrupt();
}
class Producer implements Runnable {
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !Thread.currentThread().isInterrupted()) { // 中断检测
if (num % 100 == 0) {
storage.put(num); // 会响应Interrupt中断信号
System.out.println(num + "是100的倍数,被放到仓库中了。");
}
num++;
}
...
2. 如何处理不可中断的阻塞
解答:
-
- 很遗憾,并没有一个通用的解决方案,
- 要针对某一些锁、或某一些IO给出不同的解决方案
- 比如说,对于ReentrantLock(可重入锁),如果使用Lock方法,并在lock过程中阻塞,没有办法响应interrupt中断响应的,但这个类提供了lockInterrupt这个方法,这个方法可以响应中断的
- 即针对特点情况,使用特点方法,争取能够使得它们能够响应中断