线程停止的原则:使用interrupt来通知,而不是强制停止线程

interrupt在英文中实为通知的意思,那么为什么停止线程不直接停止而使用通知呢?

这是因为在java中,我们如果想要停止一个线程,能做的最多就是告诉线程需要被中断了,而线程本身决定何时停止或者停不停止。

大多数情况我们是可以让系统中的线程运行直到结束的,或者让他们自动停止。然而其实有时候,作为开发者或者用户是希望提前结束线程的。

但是要让任务安全并且迅速停止下来并不是一件容易的事,java并没有提供任何机制来安全的停止线程,只提供了中断(interruption),这其实是一种协作机制,让一个线程去通知另外一个线程停止当前的工作。

我们很少希望某个线程的服务立即停止而不进行任何收尾工作留下一堆烂摊子,因为这样会使线程间共享的数据的状态展现出不一致的状态,那么当我们使用协作的方法去停止线程,线程会先完成手头上的工作并清除掉未开始的工作,然后再结束,这样可以保证共享资源状态的一致性,更加的安全。

实践:如何正确停止线程

线程什么情况下会停止呢?

1.run中所有方法执行完毕了 2.有异常出现且系统没有捕获

如何使用interrupt来停止线程?

通常情况下停止:

public class ThreadEndUsual implements  Runnable{@Overridepublic void run() {int num=0;//我们需要在循环判断中去响应中断while (!Thread.currentThread().isInterrupted()&&num<=Integer.MAX_VALUE/2){if(num%1000 == 0){System.out.println(num);}num++;}System.out.println("线程执行完毕!");}public static void main(String[] args) throws InterruptedException{Thread thread=  new Thread(new ThreadEndUsual());thread.start();Thread.sleep(1000);thread.interrupt();}
}

不使用thread.interrupt();语句中断线程时的结果:

使用thread.interrupt();语句中断线程时的结果:

线程可能被阻塞:

public class ThreadSleepSeldom {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);}num++;}Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}};Thread thread =new Thread(runnable);thread.start();Thread.sleep(100);thread.interrupt();}
}

执行结果:

当我们thread.interrupt();生效时,恰逢Thread.sleep(1000);生效期内,那么为什么会抛出这个异常呢?因为我们这里的catch (InterruptedException e) 的存在,线程会响应这个中断,响应中断的方式是打印出这个异常,可以从异常信息中看出:sleep被打断了。这里并不意味着系统产生了异常,有可能是sleep过程中的代码响应了中断请求,当代码中出现类似的wait,notify等都会有相应效果。

如果线程在每次循环时都阻塞

public class ThreadSleepAlways {public static void main(String[] args) throws InterruptedException{Runnable runnable =()->{int num =0;try {while(num<=30000){if(num%100==0){System.out.println(num);}num++;Thread.sleep(10);}} catch (InterruptedException e) {e.printStackTrace();}};Thread thread =new Thread(runnable);thread.start();Thread.sleep(5000);thread.interrupt();}
}

运行结果:

在每次循环时都出现sleep时,我们不需要!Thread.currentThread().isInterrupted()这句语句来在每次循环时判断是否需要中断

每次循环时都出现sleep时产生的特殊情况:线程没有停止

public class CantInterrupt {public static void main(String[] args) throws InterruptedException {Runnable runnable = ()->{int num =0;while(num<=30000){if (num%100==0){System.out.println(num);}num++;try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}};Thread thread=new Thread(runnable);thread.start();Thread.sleep(5000);thread.interrupt();}
} 

执行结果:

这是什么原因呢?

这是因为每次相应中断以后会继续下一次循环,如果我们在while括号中加入!Thread.currentThread().isInterrupted()这句语句程序是否可以停止呢?

答案非常让人惊讶,线程不会停止,为什么呢,因为java有一个机制,当线程已经响应中断后,isInterrupted()状态会被清除

由源码可见:

实际开发中到底应该以什么方式停止线程呢?

优先选择:传递中断

catch住InterruptedException以后优先在方法签名中抛出该异常(传递),同时 run()会强制try/catch

错误方式

public class PassInterrupt implements Runnable{@Overridepublic void run() {while(true){System.out.println("go");throwInMethod();}}private void throwInMethod()  {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) throws InterruptedException {Thread thread=new Thread (new PassInterrupt());thread.start();Thread.sleep(1000);thread.interrupt();}
}

结果:

思考:

因为throwInMethod存在于底层方法中,如果在其中进行try/catch顶层方法可能无法感知,导致该中断被遗漏。

应对方法:

public class PassInterrupt implements Runnable{@Overridepublic void run() {while(true){System.out.println("go");try {throwInMethod();} catch (InterruptedException e) {//保存日志/停止程序等操作System.out.println("日志已经保存");e.printStackTrace();}}}private void throwInMethod() throws InterruptedException {Thread.sleep(2000);//不在这里try/catch 是因为在这里的代码层次很深,run在较高层次,而throwInMethod较低层次}public static void main(String[] args) throws InterruptedException {Thread thread=new Thread (new PassInterrupt());thread.start();Thread.sleep(1000);thread.interrupt();}
}

为什么要这样做?

由于try/catch方法内无法抛出checked Exception异常,只能使用try/catch,顶层方法必须处理该异常,去决定中断在逻辑代码的哪一层次,并对中断进行收尾工作,避免了中断被忽略,而且避免了在底层方法中对中断的不正确处理,保证了代码的健壮性

当处于不能传递中断的状态时:恢复中断

当我们不想在方法签名中抛出异常时,但是我们需要让run受中断的影响,我们需要在catch子语句中调用Thread.currentThread().interrupt()让run方法可以感知到中断。

public class PassInterrupt2 implements Runnable {@Overridepublic void run() {while (true) {if( !Thread.currentThread().isInterrupted()) {System.out.println("程序已被中断");break;}reInterruptInMethod();}}private void reInterruptInMethod() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();Thread.currentThread().interrupt();}}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new PassInterrupt());thread.start();Thread.sleep(1000);thread.interrupt();}
}

注意:不应该屏蔽中断

什么是屏蔽中断呢?

即:既不在底层代码中抛出中断,也不在catch中恢复中断,导致中断被吞了,无法被run方法感知到,这样的做法是不可取的,因为作为被中断的线程,将中断请求吞掉,会导致线程和线程间通信的不畅。

有哪些方法可以响应中断呢?

在这些方法过程中可以感知到中断,并拥有响应中断的能力

Object.wait()/wait(long)/wait(long,int)

Thread.sleep(long)/sleep(long,int)

Thread.join()/join(long)/long(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.Rxchanger.exchange(V)

java.nio.channels.InterruptibleChannel相关方法

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

如果线程由以上的10个途径阻塞,而我们想要让他由阻塞状态中恢复,我们就可以使用thread.interrupt();对线程进行中断

用thread.interrupt();中断线程的好处

被中断的线程有如何停止线程的能力,我们不应该强制的stop线程,而是应该发出一个信号,由线程自己整理自己的工作,完成交接,自己进行处理,保证代码的完整性。

错误的停止线程的方式:

1.被弃用的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.print(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(800);} catch (InterruptedException e) {e.printStackTrace();}thread.stop();}
}

执行结果

第二组还没有完成分配则停止了,那么下次再分配时有可能视第二组未分配或者已经分配完,导致资源分配错误。

如果该程序运用于银行转账过程中,有10笔钱要同时转账,在第八笔转完后突然停止,导致的资源分配会很严重。

注:有很多说法说stop被费用是因为stop未释放已锁定的监视器,这点在oracle官网有具体指明,stop是会解锁它已锁定的监视器的。

suspend和resume会让线程挂起,在恢复之前锁不会释放,这样很容易造成程序死锁(比如唤醒它的线程需要获得锁以后才能唤醒它)。

2.用volatile设置boolean标记位

看起来是可行的:

public class WrongWayVolatile implements Runnable {//多个线程间均可以看到该volatile对象的真实值private volatile boolean canceled = false;@Overridepublic void run() {int num = 0;while (num < 10000 && !canceled) {try {if (num % 100 == 0) {System.out.println(num + "是一百的倍数");}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无法停止线程

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

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;}@Overridepublic void run() {int num = 0;try {while (num < 10000 && !canceled) {if (num % 100 == 0) {System.out.println(num + "是一百的倍数,被放到仓库中");storage.put(num);}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;}
}

执行结果:

可以看出canceled 的值已经被更改为true了,但是程序并未停止运行,并未打印出:生产者结束运行这行字,所以这段代码并未执行finally代码。

为什么会出现这种情况呢?

由代码可以看出,当我们要停止线程时,需要执行到while的判断,去判断canceled 的值才可以停止程序的运行,但是生产者代码的阻塞位置其实是:storage.put(num);这句代码,因为put代码满的时候就会停在这里,就不会再执行到while的判断,java的设计者其实考虑到了这种情况,因为interrupt是可以在程序阻塞期间被响应的,如果遇到了刚刚的情况,使用interrupt也可以及时相应中断。

修改后的代码:

public class WrongWayVolatileFixed {public static void main(String[] args) throws InterruptedException {ArrayBlockingQueue storage = new ArrayBlockingQueue(10);Producer1 producer = new Producer1(storage);Thread producerThread = new Thread(producer);producerThread.start();Thread.sleep(1000);Consumer1 consumer = new Consumer1(storage);while (consumer.needMoreNums()) {System.out.println(consumer.storage.take() + "被消费了");Thread.sleep(100);}System.out.println("消费者不需要更多数据了");//不需要更多数据则让生产者停止生产producerThread.interrupt();}}
class Producer1 implements Runnable {BlockingQueue storage;public Producer1(BlockingQueue storage) {this.storage = storage;}@Overridepublic void run() {int num = 0;try {while (num < 10000 && !Thread.currentThread().isInterrupted()) {if (num % 100 == 0) {storage.put(num);System.out.println(num + "是一百的倍数,被放到仓库中");}num++;}} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println("生产者结束运行");}}
}
class Consumer1 {BlockingQueue storage;public Consumer1(BlockingQueue storage) {this.storage = storage;}public boolean needMoreNums() {if (Math.random() > 0.95) {return false;}return true;}
}

执行结果:

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

1.interrupt方法

用一个同步获取blockerLock锁,最终调用interrupt0方法;

2.判断是否已被中断的方法

isinterrupted()获取调用对象的中断状态但是不重置

interrupted()的应用对象是当前执行的线程。

不管是哪个对象调用interrupted(),都会返回执行该句语句的线程状态,然后将中断状态重置

需要注意interrupted()和isinterrupted()的目标对象是什么,Thread.interrupted()方法的目标对象是当前线程 ,不管这个方法是哪个线程对象调用的,都应用于正在执行Thread.interrupted()这句语句的线程。

举例:

public class ThreadState implements Runnable {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {Thread t= new Thread(new ThreadState());t.start();t.interrupt();//获取中断标志System.out.println("isInterrupted:"+t.isInterrupted());//获取中断标志并重置System.out.println("Interrupted:"+t.interrupted());//获取中断标志并重置System.out.println("Interrupted:"+Thread.interrupted());//获取中断标志System.out.println("isInterrupted:"+t.isInterrupted());}
}

运行结果:

常见面试题:

1)如何停止线程

用interrupt的好处,如果要停止线程需要请求方、被请求方、子方法被调用方的配合,stop和suspend已经被废弃,volatile的boolean无法处理长时间阻塞。

2)如何处理不可中断的阻塞

当执行socketIO、可重入锁(ReentrantLock)操作时,我们使用interrupt无法响应,这种问题需要有针对性的去解决,比如可重入锁,但是它提供了lockinterruptibly()方法,这个方法是可以响应中断的,IO也有一些特定的方法来响应中断

线程停止继续_Java并发学习第二弹:如何正确停止线程?相关推荐

  1. Java多线程学习九:如何正确关闭线程池?shutdown 和 shutdownNow 的区别

    如何正确关闭线程池?以及 shutdown() 与 shutdownNow() 方法的区别?首先,我们创建一个线程数固定为 10 的线程池,并且往线程池中提交 100 个任务,如代码所示. 复制代码 ...

  2. sleep期间读取所有_java并发学习第四弹:走进JDK源码去了解sleep和join

    sleep方法详解: 作用:我只想让线程在预期的实际执行,其他时间不要占用CPU资源 特点: 1.sleep不释放锁,包括synchronized和lock和wait有很大不同 public clas ...

  3. java线程饥饿死锁_java并发-线程饥饿死锁测试

    线程饥饿死锁 <Java并发编程实践>中对线程饥饿死锁的解释是这样的:在使用线程池执行任务时,如果任务依赖于其他任务,那么就可能产生死锁问题.在单线程的Executor中,若果一个任务将另 ...

  4. java 线程工厂_Java并发编程:Java的四种线程池的使用,以及自定义线程工厂

    引言 通过前面的文章,我们学习了Executor框架中的核心类ThreadPoolExecutor ,对于线程池的核心调度机制有了一定的了解,并且成功使用ThreadPoolExecutor 创建了线 ...

  5. java线程同步的实现_Java并发编程(三) - 实战:线程同步的实现

    synchronized关键字 首先,来看一个多线程竞争临界资源导致的同步不安全问题. package com.example.weishj.mytester.concurrency.sync; /* ...

  6. java线程本地变量_Java并发编程示例(九):本地线程变量的使用

    这篇文章主要介绍了Java并发编程示例(九):本地线程变量的使用,有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享,Java并发接口提供了一种很清晰的机制来满足此需求,该机制称 ...

  7. java线程集合点_Java多线程学习笔记(三) 甚欢篇

    使人有乍交之欢,不若使其无久处之厌 <小窗幽记>很多时候,我们需要的都不是再多一个线程,我们需要的线程是许多个,我们需要让他们配合.同时我们还有一个愿望就是复用线程,就是将线程当做一个工人 ...

  8. matlab已经停止工作,win10系统运行Matlab弹出已停止工作窗口的修复步骤

    有关win10系统运行Matlab弹出已停止工作窗口的操作方法想必大家有所耳闻.但是能够对win10系统运行Matlab弹出已停止工作窗口进行实际操作的人却不多.其实解决win10系统运行Matlab ...

  9. matlab已经停止工作,win7系统运行Matlab弹出已停止工作窗口的解决方法

    朋友们在使用win7系统电脑时,偶尔就会碰到系win7系统运行Matlab弹出已停止工作窗口的状况.如果在平时的使用过程中遇到win7系统运行Matlab弹出已停止工作窗口情况的话,相信许多朋友都不知 ...

最新文章

  1. 「任务总览」优化更新,团队协作愈加高效敏捷
  2. Spring框架入门
  3. c语言程序设计网课作业答案,《C语言程序设计》作业答案
  4. 函数公开问题、简短模式、多一个返回值
  5. Dotnet创建Linux下的Service应用
  6. 全球权威MLPerf基准测试再发榜,浪潮AI服务器创18项AI性能纪录
  7. 中国信通院:二季度83款5G手机申请入网 款型数占比已过半
  8. sql智能语法提示插件 sql prompt 10
  9. Android控件 TabHost,Android控件开发之TabHost
  10. UReport2导出word报错
  11. ThunderBird 突然收不到邮件
  12. nginx验证微信文件
  13. Spring Boot Actuator 端点启用和暴露
  14. 【python系列】使用mayavi画3d散点图
  15. Matlab求常微分方程组的数值解
  16. unixbench分析_UnixBench,Bench,SuperBench 和 Zbench 四种常用云服务器综合性能测评脚本工具的对比分析以及使用教程...
  17. GADRCRERCERRHRGDWQGKQRCLMECRRREQEED|2243219-65-8
  18. 如何使用 Python 开发一个【抖音视频下载神器】
  19. 旋转数组的最小数字+堆排介绍19.9.14
  20. 自动拨号程序调试过程

热门文章

  1. Linux平台OpenGL之helloworld(十)
  2. Gstreamer调试命令(五)
  3. ip扫描工具之traceroute/nmap/fping
  4. 启动Activity不显示界面
  5. 在iOS端使用AVSampleBufferDisplayLayer进行视频渲染
  6. 命令提示符使用java 类报错_lt;03gt;详解第一个Java程序
  7. javaweb开发后端常用技术_Java Web开发后端常用技术汇总
  8. dll可以在linux下使用吗_Python 下使用 Altair 数据制图 | Linux 中国
  9. springboot做网站_Github点赞接近100k的SpringBoot学习教程+实战推荐!牛批!
  10. 华为双前置摄像头_vivo双摄像头为何前置?华为为何是后置?