如何正确停止线程?
如何正确停止线程?
原理:使用interrupt来通知,而不是强制关闭;
下面我们来写一下要获取在Integer最大值一半中某个数值的整数倍的数值:
普通情况:
/**
* 描述: run方法内没有sleep或wait方法时,停止线程
*/
public class RightWayStopThreadWithoutSleep implements Runnable {
@Override
public void run() {
int num = 0;
while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) {
if (num % 10000 == 0) {
System.out.println(num + "是10000的倍数");
}
num++;
}
System.out.println("任务运行结束了");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
结果:
0是10000的倍数
10000是10000的倍数
20000是10000的倍数
30000是10000的倍数
40000是10000的倍数
......
320200000是10000的倍数
320210000是10000的倍数
320220000是10000的倍数
320230000是10000的倍数
任务运行结束了
其中,while中的对线程是否被中断的判断只是是否进行while循环的依据。
遭遇阻塞情况
/**
* 描述: 带有sleep的中断线程的写法
*/
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 300 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
结果
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:16)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
线程在运行时执行了sleep()方法,导致线程阻塞,若在线程阻塞时执行interrupt来通知线程中断,则会抛出异常;此次情况就是探究一下在线程阻塞时对interrupt的响应。
线程在每次迭代后都阻塞的情况
/**
* 描述: 如果在执行过程中,每次循环都会调用sleep或wait等方法,那么不需要每次迭代都检查是否已中断
*/
public class RightWayStopThreadWithSleepEveryLoop {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 10000) {
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();
}
}
结果
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at RightWayStopThreadWithSleepEveryLoop.lambda$main$0(RightWayStopThreadWithSleepEveryLoop.java:14)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
这种情况与上种阻塞情况不同的是什么呢?可能细心的朋友已经发现while中的条件少了!Thread.currentThread().isInterrupted() 就是对线程是否被中断的判断,其实这是一个没有用的语句,因为在每次迭代中都会进行sleep的阻塞,而且线程大部分时间都用在了sleep中,这样当执行interrupt时,就会在某一个迭代中因为线程的阻塞而产生响应抛出异常。
While循环中的加入try/catch的情况
/**
* 描述: 如果while里面放try/catch,会导致中断失效
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num <= 10000 && ) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
结果:
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at CantInterrupt.lambda$main$0(CantInterrupt.java:16)
at java.lang.Thread.run(Thread.java:745)
500是100的倍数
600是100的倍数
700是100的倍数
800是100的倍数
......
这可能很奇怪,为什么抛出异常后还继续执行呢?可能有的小伙伴说条件判断中缺少!Thread.currentThread().isInterrupted()的判断,满足判断条件还是能继续执行,如果加上!Thread.currentThread().isInterrupted()后判断线程为中断状态就不满足执行循环的条件就会跳出循环,当我们加上后会神奇的发现,结果还是会在抛出异常后继续执行,那是为什么呢?
经过查阅是由于java语言在设计时有这样一个理念:一旦线程响应中断,就会把中断这个标记位给删除,所以在这个代码中还是会满足条件继续执行的。
实践开发中的两种最佳方案:
优先选择:传递中断:
/**
* 描述: 最佳实践: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) {
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();
}
}
如果自己负责的是run的业务逻辑部分,而只是调用别人写的throwInMethod很可能就会使得中断信号被吞掉,在run中catch这个函数的错误然后进行相关的中断就可以了。
不想或无法传递:回复中断
/**
* 描述:最佳实践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);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
利用创建的子方法来使得中断信息能够传递到run中并且能够跳出循环;
牢记:不应屏蔽中断
屏蔽中断:既不在方法签名中去抛出异常,也不在catch语句中恢复中断。
错误的使用方法:
stop、suspend和resume;
stop是因为不安全,停止线程会导致它释放掉已锁定的所有监视器;
suspend和resume是因为它让一个线程挂起,然后携带锁去休息,可能会造成死锁的情况;
volatile设置boolean标记位
①看似可行
/**
* 描述: 演示用volatile的局限:part1 看似可行
*/
public class WrongWayVolatile implements Runnable {
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数。");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
WrongWayVolatile r = new WrongWayVolatile();
Thread thread = new Thread(r);
thread.start();
Thread.sleep(5000);
r.canceled = true;
}
}
结果:
0是100的倍数。
100是100的倍数。
200是100的倍数。
300是100的倍数。
400是100的倍数。
500是100的倍数。
600是100的倍数。
700是100的倍数。
800是100的倍数。
900是100的倍数。
1000是100的倍数。
1100是100的倍数。
1200是100的倍数。
1300是100的倍数。
1400是100的倍数。
1500是100的倍数。
1600是100的倍数。
1700是100的倍数。
1800是100的倍数。
1900是100的倍数。
2000是100的倍数。
2100是100的倍数。
2200是100的倍数。
2300是100的倍数。
2400是100的倍数。
2500是100的倍数。
2600是100的倍数。
②无法在想关闭的时间关闭
/**
* 描述: 演示用volatile的局限part2 陷入阻塞时,volatile是无法停止线程的
* 此例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
*/
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
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);
}
}
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;
}
}
结果:
0是100的倍数,被放到仓库中了。
100是100的倍数,被放到仓库中了。
200是100的倍数,被放到仓库中了。
300是100的倍数,被放到仓库中了。
400是100的倍数,被放到仓库中了。
500是100的倍数,被放到仓库中了。
600是100的倍数,被放到仓库中了。
700是100的倍数,被放到仓库中了。
800是100的倍数,被放到仓库中了。
900是100的倍数,被放到仓库中了。
0被消费了
1000是100的倍数,被放到仓库中了。
100被消费了
1100是100的倍数,被放到仓库中了。
消费者不需要更多数据了。
true
当producer中stroge.put满的时候会阻塞,没有人去唤醒它,他就自然走不到while循环。无法做到停止线程。
使用interrupt修改后:
/**
* 描述: 用中断来修复刚才的无尽等待问题
*/
public class WrongWayVolatileFixed {
public static void main(String[] args) throws InterruptedException {
WrongWayVolatileFixed body = new WrongWayVolatileFixed();
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = body.new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = body.new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了。");
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);
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;
}
}
}
结果:
0是100的倍数,被放到仓库中了。
100是100的倍数,被放到仓库中了。
200是100的倍数,被放到仓库中了。
300是100的倍数,被放到仓库中了。
400是100的倍数,被放到仓库中了。
500是100的倍数,被放到仓库中了。
600是100的倍数,被放到仓库中了。
700是100的倍数,被放到仓库中了。
800是100的倍数,被放到仓库中了。
900是100的倍数,被放到仓库中了。
0被消费了
1000是100的倍数,被放到仓库中了。
100被消费了
1100是100的倍数,被放到仓库中了。
200被消费了
1200是100的倍数,被放到仓库中了。
300被消费了
1300是100的倍数,被放到仓库中了。
400被消费了
1400是100的倍数,被放到仓库中了。
500被消费了
1500是100的倍数,被放到仓库中了。
600被消费了
1600是100的倍数,被放到仓库中了。
700被消费了
1700是100的倍数,被放到仓库中了。
800被消费了
1800是100的倍数,被放到仓库中了。
900被消费了
1900是100的倍数,被放到仓库中了。
1000被消费了
2000是100的倍数,被放到仓库中了。
消费者不需要更多数据了。
生产者结束运行
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
at volatiledemo.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:46)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0