Java Thread 如何正确停止线程

Java Thread 如何正确停止线程

Java Thread 如何正确停止线程

错误的停止方法

image-20210417094943222

1、被弃用的stop、suspend和resume方法

模拟指挥军队:一共有5个连队,每个连队10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取

public class StopThread implements Runnable {

@Override

public void run() {

//模拟指挥军队:一共有5个连队,每个连队10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取

for (int i = 0; i < 5; i++) {

System.out.println("连队" + i + "开始领取武器");

for (int j = 0; j < 10; j++) {

System.out.println(j);

try {

Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println("连队"+i+"已经领取完毕");

}

}

public static void main(String[] args) {

Thread thread = new Thread(new StopThread());

thread.start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

thread.stop();

}

}

用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作(一个连队),会造成脏数据(有的连队多领取少领取装备)。

2、用volatile设置boolean标记位

演示用volatile的局限:volatile看上去可行

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;

}

}

volatile停止线程结束的错误之处

演示用volatile的局限part2 陷入阻塞时,volatile是无法正常结束线程的

此例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费

/**

* 描述: 演示用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);

}

}

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;

}

}

image-20210417100751710

Demo使用了ArrayBlockingQueue作为生产者和消费者模型的容器阻塞队列,当容器满了之后 storage.put(num);生产者队列线程挂起,阻塞等待ArrayBlockingQueue被消费。当消费者线程退出后,生产者线程还是处于阻塞状态,没有被唤醒,所以while (num <= 100000 && !canceled)得不到执行,volatile canceled就失效了

3、修正方案

/**

* 描述: 用中断来修复刚才的无尽等待问题

*/

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;

}

}

}

如何正确停止线程

image-20210417102056692

原理介绍:使用interrupt了来通知,而不是强制

使用一个线程来通知另一个线程该停止的机制,只是一种通知,如果该线程本身不决定停止,则其不会停止,被停止线程的本身,更熟悉停止自己需要做那些处理和清理工作,所以正确停止线程,是如何使用interrupt合理通知该线程并让该线程配合停止。

interrupt()方法的介绍

本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。

如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的中断状态会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。

如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。

如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。

中断一个“已终止的线程”不会产生任何操作。

image-20210417135332569

通常的停止过程(无外界干涉的情况下)- 代码运行结束

普通情况(run方法内没有sleep或wait方法时的标准写法)

/**

* 描述: 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(2000);

thread.interrupt();

}

}

线程可能被阻塞

/**

* 描述: 带有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();

}

}

如果线程在每次工作迭代之后都阻塞(调用sleep 方法等)

/**

* 描述: 如果在执行过程中,每次循环都会调用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();

}

}

中断失效问题

如果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 && !Thread.currentThread().isInterrupted()) {

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();

}

}

image-20210417135810686

停止中断的最佳实践

image-20210417135913736

catch了InterruptedExcetion之后的优先选择:在方法签名中抛出异常 那么在run()就会强制try/catch

最佳实践1:catch了InterruptedExcetion之后的优先选择:在方法签名中抛出异常 那么在run()就会强制try/catch

/**

* 描述: 最佳实践: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();

}

}

image-20210417140121940

image-20210417140611355

在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行中,依然能够检查到刚才发生了中断

/**

* 描述:最佳实践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();

}

}

image-20210417140753993

image-20210417141045958

响应中断的方法总结列表

image-20210417141135695

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. CyclicBarrier. await()

java. util. concurrent. Exchanger. exchange(V)

java.nio.channels.InterruptibleChannel相关方法

java.nio.channels.Selector的相关方法

停止线程相关的重要函数解析

image-20210417141329230

中断线程

public void interrupt()

//java.lang.Thread#interrupt

public void interrupt() {

if (this != Thread.currentThread())

checkAccess();

synchronized (blockerLock) {

Interruptible b = blocker;

if (b != null) {

interrupt0(); // Just to set the interrupt flag

b.interrupt(this);

return;

}

}

interrupt0();

}

判断是否已被中断相关方法

public boolean isInterrupted()

//java.lang.Thread#isInterrupted()

public boolean isInterrupted() {

return isInterrupted(false);

}

返回之后不会清除线程中断的状态

Thread.interrupted()

//java.lang.Thread#interrupted

public static boolean interrupted() {

return currentThread().isInterrupted(true);

}

目标对象是当前线程,返回之后会把线程中断状态设为false

/**

* 描述: 注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象

*/

public class RightWayInterrupted {

public static void main(String[] args) throws InterruptedException {

Thread threadOne = new Thread(new Runnable() {

@Override

public void run() {

for (; ; ) {

}

}

});

// 启动线程

threadOne.start();

//设置中断标志

threadOne.interrupt();

//获取中断标志

System.out.println("isInterrupted: " + threadOne.isInterrupted());

//获取中断标志并重置

System.out.println("isInterrupted: " + threadOne.interrupted());

//获取中断标志并重直

System.out.println("isInterrupted: " + Thread.interrupted());

//获取中断标志

System.out.println("isInterrupted: " + threadOne.isInterrupted());

threadOne.join();

System.out.println("Main thread is over.");

}

}

RUN>

=================打印结果======================

isInterrupted: true

isInterrupted: false

isInterrupted: false

isInterrupted: true

🌸 相关推荐 🌸

联发科是哪个国家的品牌溯源与全球影响力分析报告:从台湾半导体新星到世界级IC设计巨头
神奇照片自动美颜打印系统
365安卓版

神奇照片自动美颜打印系统

📅 07-22 👀 2644
《王者荣耀》战队赛荣誉勋章怎么换皮肤 战队赛荣誉勋章奖励兑换攻略