Java多线程-停止中断线程
# Java多线程03-停止线程
# 停止原理
停止线程并非主动、强制去停止一个线程,而是通过interrupt
来通知线程,而线程自身具有最高决定权(是否停止线程、何时停止)。
相当于interrupt
只是一种协作机制,当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。
# 如何正确停止线程
普通情况下停止线程
public class RightWayStopThreadWithoutSleep implements Runnable {
@Override
public void run() {
int num = 1;
while (num < 1000000) {
System.out.println(num);
if (Thread.currentThread().isInterrupted()) {
System.out.println("Thread is interrupted");
break;
}
num ++;
}
}
public static void main(String[] args) {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
try {
Thread.sleep(1000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
相当于一个线程去通知另一个线程你该结束了,另一个线程通过Thread.currentThread().isInterrupted()来接受通知。
阻塞情况下停止线程
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num < 300 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num);
}
num++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
线程在休眠的时候接受到停止通知,需要额外捕获异常。
线程每次迭代后都阻塞
public class RightWayStopThreadWithLoopSleep {
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);
}
num++;
Thread.sleep(10);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
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();
}
}
如果在while内部进行try/catch捕获异常,在抛出异常后,线程仍然还是运行。
# 停止线程的最佳实践
抛出中断
在方法前面中抛出签名,不要采用try/catch语句捕获异常,而是采用抛出异常的方法以便于该异常能够传递到顶层,让run方法可以捕获这一异常。
同时run方法内无法抛出异常,只能使用try/catch语句强制
处理该异常,避免了漏掉异常的情况。
public class RightWayStopThreadInProd implements Runnable{
@Override
public void run() {
while (true) {
try {
System.out.println("go");
throwException();
} catch (InterruptedException e) {
// 保存日志、停止程序等操作
System.out.println("停止程序操作");
throw new RuntimeException(e);
}
}
}
private void throwException() 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();
}
}
恢复中断
如果不想或无法传递InterruptedException(例如用run方法的时候,就不让该方法throws InterruptedException),那么应该选择在catch 子句中调用Thread.currentThread().interrupt() 来恢复设置中断状态,以便于在后续的执行依然能够检查到刚才发生了中断。
public class RightWayStopThreadInPro2 implements Runnable{
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("停止程序操作");
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 RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
# 错误停止线程
stop方法
如果我们的班级是一个最小完成单位,也就是说要么这个班级全部学生领饭,要么全部没领饭,使用stop操作就会出现某个班级学生一部分学生领饭,一部分学生没有领饭。
public class StopThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("第" + (i + 1) + "班级领饭");
for (int j = 0; j < 10; j++) {
System.out.println("第" + (j + 1) + "个学生领饭");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(1500);
thread.stop();
}
}
volatile
普通情况下,volatile的看似可行性。
public class WrongWayVolatile implements Runnable {
private volatile boolean flag = false;
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !flag) {
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 wrongWayVolatile = new WrongWayVolatile();
Thread thread = new Thread(wrongWayVolatile);
thread.start();
Thread.sleep(5000);
wrongWayVolatile.flag = true;
}
}
阻塞情况下,volatile的局限性,会出现即使修改了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);
}
}
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来处理上述案例:
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;
}
}
}