java中停止线程的方式一般是使用Interrupt的方式来对需要停止的线程发出一个停止线程的信号请求,至于该线程最后是否会立即停止这个决定权还是在该线程本身,这个机制是源于请求停止方和被停止方约定好的一种规范,它使用的是一种合作机制,那为什么java会这么设计呢?原因是我们的请求停止的线程并不知道被停止的线程在停止线程前要做哪些操作,只有这些操作做完了才会正常的停止线程,而不是请求线程信号一发出来就会直接停止线程的
(1) 通常情况下什么时候会停止线程
线程正确执行完毕后停止线程出现异常并且没有被捕获会停止(2) 正确停止线程的方法
1.普通方式,线程没有被阻塞,还在运行中的时候发出了中断信号
public class StopThreadWithoutThread implements Runnable{ @Override public void run() { int num = 0; while(num<=Integer.MAX_VALUE/2){ if (num%10000==0){ System.out.println(num+"是1000的倍数"); } num++; } System.out.println("任务运行结束了"); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new StopThreadWithoutThread()); //子线程运行 thread.start(); //主线程睡眠1秒 Thread.sleep(1000); //发出中断信号 thread.interrupt(); } } 结果 .... 1073730000是1000的倍数 1073740000是1000的倍数 任务运行结束了可以看到虽然我们加了中断信号,但是子线程并没有做出响应,这是为什么呢?我们可以加入下面的代码来试一下,在while的判断中加入是否已经被中断的判断,很显然中断起作用了
... ... while(!Thread.currentThread().isInterrupted()&&num<=Integer.MAX_VALUE/2){ ... 结果: 256300000是1000的倍数 256310000是1000的倍数 256320000是1000的倍数 任务运行结束了2.阻塞状态,发出中断信号
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++; } //休眠1秒,期间接收到中断信号 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }; Thread thread = new Thread(runnable); thread.start(); //主线程休眠0.5秒 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 thread.createthread.StopThreadwithSleep.lambda$main$0(StopThreadwithSleep.java:14) at java.lang.Thread.run(Thread.java:748)说明子线程在阻塞状态下是可以响应中断信号的,响应的手段是抛出异常
3.替换isInterrupted来响应中断
既然线程阻塞状态下可以响应中断,那么我们之前的线程中断判断语句Thread.currentThread().isInterrupted()是否可以被替换,可以试一下,我们把判断语句去除,将sleep方法移入到while方法里面,意思相当于每次循环都会进入到阻塞状态,自然只要中断请求,就能及时响应
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(); //5秒后进行响应 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 thread.createthread.RightWayStopThreadWithEveryLoop.lambda$main$0(RightWayStopThreadWithEveryLoop.java:13) at java.lang.Thread.run(Thread.java:748)4.中断无法被正确处理的情况(当try,catch在循环条件中)
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,catch语句是在while循环里面 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 thread.createthread.RightWayStopThreadWithEveryLoop.lambda$main$0(RightWayStopThreadWithEveryLoop.java:15) at java.lang.Thread.run(Thread.java:748) 500是100的倍数 600是100的倍数哦吼,没有被中止诶,可能会有人说你没加终止判断语句,我们在来试试
... while (!Thread.currentThread().isInterrupted()&&num <= 10000) { ....其实加上去结果是一样的,那是为什么呢,其实当sleep在响应了中断请求之后,会将子线程的中断状态去除,这个时候进入到下一个while判断逻辑时是检查不到它的中断状态的也就无法判断它是否 中断,所以它就能够继续执行,所以切记不要将中断的try,catch写在循环内部,这样是无法跳出循环的,除非你再catch中主动break该循环
(3)正式开发中停止线程的正确方式
1.优先选择:传递中断(将中断向上抛出,由顶层run方法来处理中断)
public class RightWayStopThreadInProd implements Runnable{ @Override public void run() { try{ int num = 1; while(true){ System.out.println("程序执行中"+num); InterruptMethod(); num++; } }catch (InterruptedException e){ System.out.println("发现中断信号,需要停止线程"); System.out.println("停止前逻辑处理,保存日志等........"); System.out.println("处理完毕正式停止线程"); } } //这里将本可以在InterruptMethod方法里处理的异常抛出去 public void InterruptMethod() throws InterruptedException { Thread.sleep(1000); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new RightWayStopThreadInProd()); thread.start(); //5秒后发出中断信号 Thread.sleep(5000); thread.interrupt(); } } 结果: 程序执行中1 程序执行中2 程序执行中3 程序执行中4 程序执行中5 发现中断信号,需要停止线程 停止前逻辑处理,保存日志等........ 处理完毕正式停止线程2.不想或无法传递中断时,可以通过恢复中断的方式来实现中断信号传递
public class RightWayStopThreadInProd implements Runnable{ @Override public void run() { int num = 1; while(true){ //判断底层是否有中断信号传入 if (Thread.currentThread().isInterrupted()){ System.out.println("顶层发现中断信号,需要停止线程"); System.out.println("停止前逻辑处理,保存日志等........"); System.out.println("处理完毕正式停止线程"); break; } System.out.println("程序执行中"+num); InterruptMethod(); num++; } } //自己处理异常,并向上传递中断信号 public void InterruptMethod() { try { Thread.sleep(1000); } catch (InterruptedException e) { //其实这里中断信号已经被清除,但是我们又调用了中断方法恢复了中断信号 System.out.println("发送中断,正在恢复底层中断到顶层...."); Thread.currentThread().interrupt(); System.out.println("================="); } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new RightWayStopThreadInProd()); thread.start(); //5秒后发出中断信号 Thread.sleep(5000); thread.interrupt(); } } 结果: 程序执行中1 程序执行中2 程序执行中3 程序执行中4 程序执行中5 发送中断,正在恢复底层中断到顶层.... ================= 顶层发现中断信号,需要停止线程 停止前逻辑处理,保存日志等........ 处理完毕正式停止线程除了sleep方法能够响应中断还有以下方法也能够响应 Object.wait()/wait(long)/wait(long,int) Thread.join()/join(long)/join(long,int) java.util.concurrent.BlockingQueue.take()/put(E) java.util.concurrent.locks.Lock.lockInterrruptibly() java.util.concurrent.CountDownLatch.await() java.util.concurrent.CyclicBarrier.await() java.util.concurrent.Exchanger.exchange(V) java.nio.channels.InterruptibleChannel相关方法 java.nio.channels.Selector的相关方法 所以这些方法的中断我们都应该正确的处理
(4) 停止线程错误的方法
用stop()来停止线程,会导致线程运行一半突然停止 public class WrongwayStopThread implements Runnable{ @Override public void run() { int num = 1; while (true){ System.out.println("业务"+num+"处理开始"); for (int i = 0; i < 10; i++) { System.out.println("业务"+num+"的第"+i+"个任务处理中..."); try { Thread.sleep(150); } catch (InterruptedException e) { System.out.println("我被中断了,我需要处理一些善后工作"); System.out.println("现在执行到第"+num+"个业务的,该业务执行到一半需要抛弃,避免脏数据"); e.printStackTrace(); } } System.out.println("业务"+num+"处理结束"); num++; } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new WrongwayStopThread()); thread.start(); Thread.sleep(5000); thread.stop(); } } 结果: 业务3的第9个任务处理中... 业务3处理结束 业务4处理开始 业务4的第0个任务处理中... 业务4的第1个任务处理中... 业务4的第2个任务处理中...可以发现当我们执行stop方法的时候,业务并没有进入到我们的中断异常处理阶段,而是直接戛然而止,这其实是相当不友好的,除非我们的业务在发生中断后无需做任何处理
2.suspend将线程挂起,运行->阻塞; 调用后并不释放所占用的锁(不释放锁,可能会导致死锁) 3.resume将线程解挂,阻塞->就绪(不释放锁,可能会导致死锁)
4.使用volatile关键字设置标志位来停止线程
public class WrongWayVolatile implements Runnable{ private volatile boolean cancel = false; @Override public void run() { int num = 0; try{ while(num<100000&&!cancel){ if (num % 100 == 0) { System.out.println(num + "是100的倍数。"); } num++; Thread.sleep(1); } }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("程序执行结束"); System.out.println("现在的标志符是"+cancel); } public static void main(String[] args) throws InterruptedException { WrongWayVolatile rwv = new WrongWayVolatile(); Thread thread = new Thread(rwv); thread.start(); Thread.sleep(5000); rwv.cancel = true; } } 结果: 3700是100的倍数。 3800是100的倍数。 3900是100的倍数。 4000是100的倍数。 程序执行结束 现在的标志符是true看似结果可行,但实际上其中存在着不足我们来继续看下面的一段代码
//1.生产者 public class Producer implements Runnable{ //停止标志位 public volatile boolean cancel = false; //队列仓库 BlockingQueue storage; public Producer(BlockingQueue storage){ this.storage = storage; } @Override public void run() { int num = 0; try { while (num <= 100000 && !cancel) { if (num % 100 == 0) { //我在这里可能被阻塞,就算cancel标志位被置为true也无法进入到下一个循环 storage.put(num); System.out.println(num + "是100的倍数,被放到仓库中了。"); } num++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("生产者结束运行"); } } } //2.消费者 public class Consumer { //消费者队列仓库 BlockingQueue storage; public Consumer(BlockingQueue storage){ this.storage = storage; } public boolean needMoreNums(){ if (Math.random() > 0.95){ return false; } return true; } } //主类 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; } } 执行结果: ..... 1100被消费了,此时仓库还有9个数据需要消费 2100是100的倍数,被放到仓库中了。 1200被消费了,此时仓库还有9个数据需要消费 2200是100的倍数,被放到仓库中了。 消费者不要跟多数据了。 true貌似好像也没有什么问题,但是实际上线程是没有真正的被停止
那么是由于什么原因造成这种情况呢,实际上虽然我们将标志位设置为true,但是在生产者线程线程在storage.put(num);这句语句中陷入了阻塞,因为它10个位置已经被塞满了,所以无法继续放入需要的数据,它就卡在这了,此时我们发出cancel为ture的信号,它是无法进入到下一个while的判断语句中所以也就出现了这种情况我们无法停止线程,换句话说就是当线程陷入阻塞的状态时是无法通过标志位的方式来响应线程中断逻辑的,那么我们如何进行改进一下呢?其实我们最好不要用标志位里处理这种逻辑,所以改用中断的方式进行处理修改代码如下
//生产者 public 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的倍数,被放到仓库中了。"); System.out.println("仓库中的数量"+storage.size()); } num++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("生产者结束运行"); } } } //消费者 public class Consumer { //消费者队列仓库 BlockingQueue storage; public Consumer(BlockingQueue storage){ this.storage = storage; } public boolean needMoreNums(){ if (Math.random() > 0.95){ return false; } return true; } } //主函数 public class WrongWayVolatileCanNotStop { 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(); //睡眠一秒,此时producer线程生产队列已经被塞满,需要consumer进行消费 Thread.sleep(1000); Consumer consumer = new Consumer(storage); while (consumer.needMoreNums()){ System.out.println(consumer.storage.take()+"被消费了,此时仓库还有"+consumer.storage.size()+"个数据需要消费"); Thread.sleep(100); } System.out.println("消费者不要跟多数据了。"); //一旦消费者不要更多数据,我们应该让生产者也停下来 producerThread.interrupt(); } } 结果: 仓库中的数量10 2600被消费了,此时仓库还有9个数据需要消费 3600是100的倍数,被放到仓库中了。 仓库中的数量10 消费者不要跟多数据了。 生产者结束运行 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 thread.createthread.Producer.run(Producer.java:21) at java.lang.Thread.run(Thread.java:748) Process finished with exit code 0通过使用中断的方式发现线程最终是被中断了,也就解决了刚才线程阻塞的问题
5.停止线程相关重要函数解析
static boolean interrupted()判断当前线程是否被中断,调用后会把中断线程直接设为false,即清除中断标志boolean isInterrupted() 判断当前线程是否被中断,不会清除中断标志Thread.interrupted()的目标对象是当前执行该静态方法的线程而不管是谁调用的 public static void main(String[] args) throws InterruptedException { Thread sonThread = new Thread(()->{ for (; ; ) { } } ); //启动线程 sonThread.start(); //设置中断标志位---> true表示已中断 sonThread.interrupt(); //获取中断标志位,并不清除 ---> 不清除,获取到true System.out.println(sonThread.isInterrupted()); //获取中断标志并重置 System.out.println(sonThread.interrupted()); //获取中断标志位并重置 System.out.println(Thread.interrupted()); //获取中断标志位 System.out.println(sonThread.isInterrupted()); sonThread.join(); System.out.println("Main thread is over!"); //结果: true false false trueinterrupted()是静态方法无论是对象调用还是类调用判断的都是主线程的中断标志,所以应该都是false
6.面试问题
(1)如何正确的停止线程
用interrupt来请求线程停止而不是强制,好处是安全想停止线程,要请求方、被停止方、子方法被调用方相互配合才行:作为被停止方:每次循环中或者适时检查中断信号,并且在可能抛出InterrupedEXception的地方处理该中断信号
请求方:发出中断信
子方法调用方(被线程调用的方法的作者)要注意:优先在方法层面抛出Interruped Exception,或者检查到中断信号时,再次设置中断状态;
最后再说错误的方法:stop/suspend已废弃,volatile的boolean无法处理长时间阻塞的情况如何处理不可中断的阻塞
如果线程阻塞是由于调用了wait(),sleep()或join()方法,你可以中断线程,通过抛出 InterruptedException异常来唤醒该线程。
但是对于不能响应 InterruptedException的阻塞,很遗憾,并没有—个通用的解决方但是我们可以利用特定的其它的可以响应中断的方法,比如ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。
答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。
总结就是说如果不支持响应中断,就要用特定方法来唤起,没有万能药