多线程之线程中断
前言
线程的优雅中断是一个需要关注的问题,本次记录下学习的情况
一、几个案例
public class InteruptThread1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int i=0;
while (!Thread.currentThread().isInterrupted()) {
System.out.println(i);
i++;
}
}
});
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
最简单的例子,当调用 thread.interrupt();就会中断线程,因为我们上面判断了线程中断标志位
public class ThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int num = 0;
while (num < 5000 && !Thread.currentThread().isInterrupted()) {
if(num%3==0){
System.out.println(num);
}
num++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}
线程中带有Thread.sleep(),上面的例子是我们上面的方法逻辑很快就执行完了,也就是到下面的休眠状态了,然后我们调用了线程中断后,因为sleep可以唤醒中断,因此触发异常
public class ThreadEveryLoopSleep {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int num = 0;
while (num < 5000 && !Thread.currentThread().isInterrupted()) {
if (num % 3 == 0) {
System.out.println(num);
}
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
这个案例是每次循环都包含sleep,我们发现即使我们加了线程中断的判断标志,其实线程并没有停下来,因为sleep设计再响应中断后就会清除中断标志,所以没有效果
二、两种最佳实践
1.传递中断(优先选择)
代码如下(示例):
public class RightWayStopThread1 implements Runnable {
@Override
public void run() {
boolean flag=true;
while (flag) {
System.out.println("test");
try {
throwInMethod();
} catch (InterruptedException e) {
System.out.println("逻辑处理,停止线程,或者保存日志");
flag=false;
e.printStackTrace();
}
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(1000);
}
public static void main(String[] args) throws InterruptedException {
RightWayStopThread1 rightWayStopThread1 = new RightWayStopThread1();
Thread thread = new Thread(rightWayStopThread1);
thread.start();
Thread.sleep(10);
thread.interrupt();
}
}
这里我们要再方法签名里面抛出异常,然后交由上层去可以捕获到当前的异常,然后可以进行有关的处理。
2.恢复中断
代码如下(示例):
public class RightWayStopThread2 implements Runnable{
@Override
public void run() {
while (true) {
if(Thread.currentThread().isInterrupted()){
System.out.println("保存日志等");
break;
}
System.out.println("test");
throwInMethod();
}
}
private void throwInMethod(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
RightWayStopThread2 rightWayStopThread2 = new RightWayStopThread2();
Thread thread = new Thread(rightWayStopThread2);
thread.start();
Thread.sleep(10);
thread.interrupt();
}
}
这里我们没法在方法签名里面抛出,也就是我们自己去捕获了线程中断的异常,那么这个时候我们就要去恢复中断,告诉我们的线程,你已经被中断了哦。
三、Volatile标志位的不适当场景
public class WrongVolatile {
static class Producer implements Runnable{
public volatile boolean cancle=false;
BlockingQueue<Integer> blockingQueue;
public Producer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
int num=0;
try {
while (num <= 100000&& !cancle){
if(num%100==0){
blockingQueue.put(num);
System.out.println(num + "放入了仓库!");
}
num++;
} } catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("生产者已经结束生产");
}
}
}
static class Consumer {
BlockingQueue<Integer> blockingQueue;
public Consumer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public boolean needMoreNums(){
if(Math.random()>0.95){
return false;
}
return true;
}
}
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(10);
Producer producer = new WrongVolatile.Producer(blockingQueue);
new Thread(producer).start();
//保证队列满
Thread.sleep(1000);
//消费者消费
Consumer consumer = new Consumer(blockingQueue);
while (consumer.needMoreNums()){
System.out.println(blockingQueue.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了");
producer.cancle=true;
}
}
这里就会发现Volatile失效了,因为当生产者满了的时候,他会阻塞在blockingQueue.put(num);放数的这个步骤,根本不会走出循环,也就不会到上面的条件判断,因此即使标志位改变了,也无法终止线程的运行。
总结
线程中断的最佳实践,要么我们传递中断,让上层方法得知线程是中断的,要么恢复中断,我们虽然自己处理的线程中断,但是要恢复这个状态,让线程知道自己被中断了!其次使用Volatile标志位在发生阻塞的时候是无法终止线程的哦!