吉林整站优化,佛山网站建设咨询,免费追漫软件app,自学网站建设基本流程3、核心三#xff1a;正确的停止线程
3.1 如何正确的停止线程
3.1.1 原理介绍#xff1a;使用interrupt来通知#xff0c;而不是强制
线程中断的机制#xff1a;一个线程来通知要中断线程#xff08;你好#xff0c;你现在应该停止了#xff09;最后的决定是由要中断…3、核心三正确的停止线程
3.1 如何正确的停止线程
3.1.1 原理介绍使用interrupt来通知而不是强制
线程中断的机制一个线程来通知要中断线程你好你现在应该停止了最后的决定是由要中断程序来执行。如果要中断程序不想停止那么我们也无能无力这是合理的why我们停止一个线程需要很多步骤比如存储停止前线程的状态等这些步骤由当前线程自己来执行比较好自己很清楚自己的情况
3.1.2 通常线程会在什么情况下停止
run方法全部执行完毕最常见存在异常方法也没有捕获线程会停止
3.1.3 正确的停止方法interrupt这里会给出不同情况我们应该如何停止线程
情况1通常线程会在什么情况下停止
/*** run方法没有sleep或wait方法时停止线程*/
public class RightWayStopThreadWithoutSleep implements Runnable{Overridepublic void run () {int num 0;while(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();}
}
//这种情况下线程被中断后run方法还是会执行完毕
//因为方法中没有能够响应中断的机制注意线程早就被停止了只不过run方法还在执行正确停止线程的方法
/*** run方法没有sleep或wait方法时停止线程*/
public class RightWayStopThreadWithoutSleep implements Runnable{Overridepublic void run () {int num 0;while(!Thread.currentThread().isInterrupted() numInteger.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();}
}
//这种情况下我们在循环里面加入了判断条件 !Thread.currentThread().isInterrupted()
//此时run方法能被停止情况2线程可能被阻塞
/*** 带有sleep的中断线程的用法该段代码正确的停止线程* sleep方法能够响应中断机制*/
public class RightWayStopThreadWithSleep {public static void main (String[] args) throws InterruptedException {Runnable runnable ()-{int num 0;try {while(num300 !Thread.currentThread().isInterrupted()){if(num % 100 0){System.out.println(num是100的倍数);}num;}Thread.sleep(1000);//k} catch (Exception e) {e.printStackTrace();System.out.println(Thread.currentThread().isInterrupted());}};Thread thread new Thread(runnable);thread.start();Thread.sleep(500);//500的设计是为了实现中断线程时我们正在执行kthread.interrupt();}
}输出 看到没有在调用thread.interrupt()语句时我们正处于run方法中的Thread.sleep(1000)。此时线程正在被sleep方法阻塞我们停止线程成功。为什么是成功了呢明明最后输出false这个原因看到后面情况四就明白了
情况3如果线程在每次迭代循环后都阻塞
/*** 每次循环中都有sleep或wait方法,那么不需要每次迭代都检查是否中断该段代码正确的停止线程* !Thread.currentThread().isInterrupted()*/
public class RightWayStopThreadWithSleepEveryLoop {public static void main (String[] args) throws InterruptedException {Runnable runnable ()-{int num 0;try {while(num10000){if(num % 100 0){System.out.println(num是100的倍数);}num;Thread.sleep(10);}} catch (Exception e) {e.printStackTrace();System.out.println(Thread.currentThread().isInterrupted());}};Thread thread new Thread(runnable);thread.start();Thread.sleep(5000);thread.interrupt();}
}输出 我们成功停止了线程。报错原因和上面一样
情况4while内try/catch的问题
/*** 如果while里面放try/catch会导致中断失效*/
public class CantInterrupt {public static void main (String[] args) throws InterruptedException {Runnable runnable ()-{int num 0;while(num10000!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();}
}输出 我们停止线程失败了。因为Java语言在设计sleep函数的时候有这样的一个理念。就是当他一旦响应中断便会把线程的interrupt标记位给清除。也就是说在刚刚的情况下它确实在sleep过程中收到了中断而且也catch并打印出异常信息。但是由于sleep设计的理念导致interrupt标记位被清除。所以我们每次循环判断Thread.currentThread().isInterrupted()都会返回false。还有看看try_catch放在循环里面catch打印异常信息之后循环继续
正确的停止这种情况下的线程方法在后面
情况5实际开发中的两种最佳实践 优先选择传递中断 1 不想或无法传递恢复中断 2 不应屏蔽中断吞了中断信息例如第一种实践中的try语句这种情况非常不可取 第一种最佳实践优先选择传递中断 这里的传递就是指异常的传递即throws异常 /*** 最佳实践最好的停止线程方法catch了InterruptedExcetion之后的优先选择* 在方法签名中抛出异常* 那么在run方法中就会强制要求try/catch* 下面的代码是错误的示范*/
public class RightWayStopThreadInProd implements Runnable{Overridepublic void run () {while(true){System.out.println(go);throwInMethod();}}private void throwInMethod(){try {Thread.sleep(2000);//k} catch (InterruptedException e) {//在执行k时我们收到中断信号。此时我们代码在此处进行了消极处理//try/catch将中断信号吞了没有上报领导run导致领导没有收到中断通知中断失败e.printStackTrace();}}public static void main (String[] args) throws InterruptedException {Thread thread new Thread(new RightWayStopThreadInProd());thread.start();Thread.sleep(1000);thread.interrupt();}
}输出 我们中断成功了。只不过run还没有停止执行。判断Thread.currentThread().isInterrupted()都会返回false。 处理方法try/catch改为throws public class RightWayStopThreadInProd implements Runnable{Overridepublic void run () {while(true !Thread.currentThread().isInterrupted()){System.out.println(go);try {throwInMethod();} catch (InterruptedException e) {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还没有停止执行。判断Thread.currentThread().isInterrupted()都会返回false。 第二种最佳实践不想或无法传递恢复中断 /*** 最佳实践2在catch语句中调用Thread.currentThread().interrupt()来恢复中断状态* 以便于在后续的执行中依然能够检查到刚才发生了中断* 回到刚才RightWayStopThreadInProd补上中断让他跳出run*/
public class RightWayStopThreadInProd2 implements Runnable{Overridepublic void run () {while(true){System.out.println(1);if(Thread.currentThread().isInterrupted()) {System.out.println(程序运行结束);break;}reInterrupt();}}private void reInterrupt() {try {Thread.sleep(3000);} catch (InterruptedException e) {Thread.currentThread().interrupt();//这条语句解决了try语句独吞中断信号e.printStackTrace();}}public static void main (String[] args) throws InterruptedException {Thread thread new Thread(new RightWayStopThreadInProd2());thread.start();Thread.sleep(2000);thread.interrupt();}
}输出 我们正确的停止了线程并且停止了run方法的继续执行 现在为了显示Thread.currentThread().interrupt();这条语句的重要性。我们注释掉他结束输出 这条语句是解决try语句独吞中断信号的情况导致run知道中断信息以便于停止run方法
情况6响应中断的方法总结列表
响应中断表示当我们执行wait()方法时如果有中断信号过来了我们可以知道中断过来了
例如 Thread.sleep 和 Object.wait 等都会检查线程何时中断并且在发现中断时提前返回。他们在响应中断时执行的操作包括清除中断状态抛出 InterruptedException 表示阻塞操作由于中断而提前结束。 3.1.4 正确停止带来的好处使用interrupt来中断的好处
被中断的线程拥有控制是否中断的权限。这一点非常重要被中断的线程自己最了解自己数据的备份、线程状态的存储自己亲自来进行比较合理。这样的做法我们保证了线程的安全和完整。
3.2 interrupt()源码分析
再次强调该方法只是通知一下设置线程的中断标志为true不是强制执行
public class Thread implements Runnable {public void interrupt() {if (this ! Thread.currentThread()) {checkAccess();// thread may be blocked in an I/O operationsynchronized (blockerLock) {Interruptible b blocker;if (b ! null) {interrupted true;interrupt0(); // inform VM of interruptb.interrupt(this);return;}}}interrupted true;// inform VM of interruptinterrupt0();}//关键就在这里但这是C的代码。老师在彩蛋里面有明确的说明如何查看native代码private native void interrupt0();
}判断是否已被中断相关方法 public static boolean interrupted() 1 public boolean isInterrupted() 2 作用检测当前线程是否被中断方法1在返回结果时直接清除线程的中断状态注意一个线程被清除中断状态后再次调用方法判断是否中断时返回false除非当前线程在第一次调用清除其中断状态之后和第二次调用检查它之前再次中断方法2返回目标线程的中断状态方法1他的目标对象应该是当前线程执行这条语句的线程。而不是方法调用者
源码 static boolean interrupted() /**
* 测试当前线程是否已中断。此方法清除线程的 中断状态 。换言之如果连续调用此方法两次则第二次调* 用将返回 false除非当前线程在第一次调用清除其中断状态之后和第二次调用检查它之前再次中断。
* 返回true 如果当前线程已中断; false 否则。
*/
public static boolean interrupted() {//true,说明清除中断标志return currentThread().isInterrupted(true);
}boolean isInterrupted() public boolean isInterrupted() {//false,说明不清除中断标志return isInterrupted(false);
}练习
/*** 注意Thread.interrupted()方法的目标对象是“当前线程 ”* 而不管本方法是由谁来调用的*/
public class RightWayInterrupted {public static void main (String[] args) {Thread thread new Thread(new Runnable() {Overridepublic void run () {}});thread.start();thread.interrupt();System.out.println(thread.isInterrupted());System.out.println(thread.interrupted());//此方法处理对象是执行这条语句的线程mainSystem.out.println(Thread.interrupted());//此方法处理对象是执行这条语句的线程mainSystem.out.println(thread.isInterrupted());}
}输出
true
false
false
true3.3 错误的停止方法两种
被弃用的stop,suspend和resume方法
/*** 错误的停止线程用stop()来停止线程会导致线程运行一半时突然停止没办法完成一个基本单位的操作* 会造成脏数据*/
public class StopThread implements Runnable{Overridepublic 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();//此时停止线程会发生错误}
}输出
联队0开始领取武器
0
1
2
3
4
5
6
7
8
9
联队0领取武器了
联队1开始领取武器
0
1
2
3
4
5我们发放弹药是以连队为单位的错误的停止线程使联队1中有人领取了有人没有领取。这样的结果是大忌。千万要避免
注意事项
关于stop理论错误的说法不使用stop方法是因为该方法不会释放所有的监视器suspend不会释放锁可能会造成死锁
用volatile设置boolean标记位
/*** 演示volatile的局限* part1:看似可行*/
public class WrongWayVolatile implements Runnable{private volatile boolean canceled false;Overridepublic void run () {int num 0;try {while (num100000 !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 aVolatile new WrongWayVolatile();Thread thread new Thread(aVolatile);thread.start();Thread.sleep(5000);aVolatile.canceled true;}
}输出 在这种情况下我们确实实现了线程的终止
但是还有一种情况我们没有实现停止线程的任务。在《并发编程实战》中对这种情况描述为 如果使用这种方法用volatile设置boolean标记位的任务调用了一个阻塞方法例如 BlockingQueue.put那么可能会产生一个更严重的问题——任务可能永远不会检查取消标志因此永远不会结束 /*** 演示用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(10);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{BlockingQueue storage;//存放生产物品的仓库public volatile boolean canceled false;public Producer (BlockingQueue storage) {this.storage storage;}Overridepublic void run () {int num 0;try {while (num100000 !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;}
}结果 原因 :
一旦队列满了put方法也会被阻塞。当生产者在put方法中阻塞时如果消费者希望取消生产者任务那消费者使用producer.canceled true;。那么就会发生上面的结果。因为生产者无法从阻塞的put方法中恢复过来因为队列满了并且消费者停止了
一旦仓库满了storage.put(num);就不会执行下面的所有代码。因此我们线程停止失败了
while (num100000 !canceled){if(num % 100 0){storage.put(num);System.out.println(num是100的倍数,它已经被放到仓库中);}num;
}我们看看这个方法的具体实现 对于上面的生产者消费者模型我们使用正确的停止线程的方式来终止线程
/*** 用中断来修复刚才的无尽等待问题*/
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;}Overridepublic 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;}}
}输出 3.4 常见面试题
如何停止线程
使用interrupt来请求中断好处保证数据安全主动权在被中断的线程手上要想成功中断需要三方相互配合请求方、被停止方、子方法被调用方最后再说错误中断的方法stop/suspend已弃用volatile的boolean无法处理长时间阻塞的情况
如何处理不可中断的阻塞
如果线程阻塞是由于调用了 wait()sleep() 或 join() 方法你可以中断线程通过抛出 InterruptedException 异常来唤醒该线程。但是对于不能响应InterruptedException的阻塞很遗憾并没有一个通用的解决方案。但是我们可以利用特定的其它的可以响应中断的方法比如ReentrantLock.lockInterruptibly()比如关闭套接字使线程立即返回等方法来达到目的。总结就是说如果不支持响应中断就要用特定方法来唤起没有万能药。
3.5 关于run方法不能抛出异常的讨论
看代码 此时IDEA报错
‘run()’ in ‘Anonymous class derived from java.lang.Runnable’ clashes with ‘run()’ in ‘java.lang.Runnable’; overridden method does not throw ‘java.lang.Exception’
我们只能try 原因请看下面的源码
FunctionalInterface
public interface Runnable {/*** When an object implementing interface {code Runnable} is used* to create a thread, starting the thread causes the objects* {code run} method to be called in that separately executing* thread.* p* The general contract of the method {code run} is that it may* take any action whatsoever.** see java.lang.Thread#run()*/public abstract void run();
}如有不对的地方请在评论区指正