原理介绍:
使用interrupt来通知,而不是强制
最佳实践: 如何正确停止线程
- 通常的停止过程(无外界干涉的情况下)
- run()方法执行完毕
- 有一点异常出现,但没有被捕获
- 正确方法: 用interrupt来请求停止线程
- 普通情况(run方法内没有sleep或wait方法的标准写法)
- 线程可能被阻塞
- 如果线程在每次工作迭代后都阻塞(调用sleep方法等)
- 如果不这样写,会遇到的问题: 线程无法停止
- while内try/catch的问题: java语言在设计sleep函数的时候的理念就是一旦中断,就是清除interrupt标记位
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
try {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
- 实际生产开发时要注意的编码习惯
- 两种最佳处理方式
- 优先选择: 传递中断,直接throw exception
- 不想或无法传递: 恢复中断, 通过
Thread.currentThread().interrupt();
- 不应该屏蔽中断
- 两种最佳处理方式
- 可以为了响应中断而抛出interruptedException的常见方法列表总结
- Object.wait()/wait(long)/wait(long,int)
- Thread.sleep(long)/sleep(long,int)
- Thread.join()/join(long)/join(long,int)
- java.util.concurrent.BlockingQueue.take()/put(E)
- java.util.concurrent.locks.Lock.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.concurrent.Exchanger.exchange(V)
- java.util.concurrent.CyclicBarrier.await()
- java.nio.channels.InterruptibleChannel相关方法
- java.nio.channels.Selector的相关方法
错误的停止方法
- 被弃用的stop、suspend和resume方法
- 使用stop的后果
- 用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作(一个连队), 会造成脏数据(有的连队会多领取少领取装备)
- 关于stop的错误理论
- 使用stop不能释放锁,这是错误的
- suspend的问题
- suspend使线程暂停,但是不会释放类似锁这样的资源,时间久了会造成死锁
- resume: 使线程恢复,如果之前没有使用suspend暂停线程,则不起作用。
- 使用stop的后果
- 用volatile设置Boolean标记位
- 看上去可行
- 错误之处
- 阻塞住了,没有去校验
- 修正方式
- 通过interrupt方法解决,而不是volatile
- 演示volatile失效方式
/**
* 演示用volatile的局限 part2: 陷入阻塞时,volatile是无法停止线程的
* 此例中,生产者的生产速度很快,消费者消费速度慢
* 所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
*/
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Object> storage = new ArrayBlockingQueue<>(10);
Producer producer = new Producer(storage);
Thread thread = new Thread(producer);
thread.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;
}
}
class Producer implements Runnable {
BlockingQueue storage;
public volatile boolean canceled = false;
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() {
return Math.random() <= 0.95;
}
}
停止线程相关的重要函数解析
- 中断线程
- interrupt方法原理分析
- 判断是否已经被中断
- static boolean interrupted()
- boolean isInterrupted()
- 举例说明,注意Thread.interrupted()的目的对象是“当前线程”,
常见面试问题
- 如何停止一个线程
- 原理: 用interrupt来请求、好处
- 想停止线程,要请求方、被停止方、子方法被调用方互相配合
- 最后再说错误的方法: stop/suspend已废弃,volatile的Boolean方法无法处理长时间阻塞的情况
- 如果处理不可中断的阻塞(例如抢锁时ReentrantLock.lock()或者Socket I/O时无法响应中断,那应该怎么让线程停止呢?)