java多线程之“线程中断”的理解

Wesley13
• 阅读 273

什么时候要用到中断:

  比如你开了生产者Producer和消费者Consumer两个线程,并用一个同步队列放置Porducer生产的和Consumer消费的产品。在Consumer中开启一个Producer线程,并且当Consumer不想消费时,可以随时结束掉Producer线程。如果不使用中断,可以使用一个boolean canceled标志,在Producer中循环检查该标志,如果该标志为true则退出。

try{
    while(! cancelled){
        product = Producer.produce();
        blockingQueue.put(product);
    }
}catch(InterruptedException){}

  可是当blockingQueue被填满,Producer线程被堵塞在put()方法时,Consumer将cancelled设为true,并停止消费。在这种情况下,Producer线程将一直阻塞在put()方法处,永远检测不到cancel被置为true。

  BlockingQueue.put()方法是一个阻塞库方法,它可以及时检测到线程的中断状态。因此如果在Consumer中使用interrupt()方法中断Producer,那么即使Producer阻塞在put()处了,也能及时响应中断请求,结束生产线程。

线程中断到底是什么:

  “线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能的情况下停止当前工作。”

  每个线程都有一个boolean类型的中断变量,并且在Thread类中有三个方法,分别是

public class Thread{
    public void interrupt(){...}
    public boolean isInterrupted(){...}
    public static boolean interrupted(){...}
    ...
}

  其他线程可以调用线程的interrupt()方法,通过将中断变量设为true来中断该线程;isInterrupted()方法返回线程的中断变量状态,但不会改变其状态值;而interrupted()方法会先清除中断状态(即将中断变量设为false),并返回之前的中断状态。

System.out.println("thread is interrupted: "  + Thread.currentThread().isInterrupted());
            Thread.currentThread().interrupt();
            System.out.println("thread is interrupted: "  + Thread.currentThread().isInterrupted());
            System.out.println("thread is interrupted: "  + Thread.interrupted());
            System.out.println("thread is interrupted: "  + Thread.currentThread().isInterrupted());

  输出结果:

thread is interrupted: false
thread is interrupted: true
thread is interrupted: true
thread is interrupted: false

  可以看到,在使用interrupted()类方法后,线程的中断状态变为false。

怎么使用中断机制

  其实,中断操作(即调用线程的interrupte()方法)并不是会停止一个正在运行的线程,它只是把线程里边的中断变量设置为true,向这个线程发出一个中断请求,而线程怎么反应,则是线程自己的事情。它可以完全不理会中断,照常执行。举个栗子

public class interruptTest {
    private static class Endless extends Thread{
        public void run(){
            int i=0;
            while (i<500){
                i++;
                System.out.println(i);
            }
        }
    }
    public static void main(String[] args) throws InterruptedException{
        Thread endless = new Endless();
        endless.start();
        Thread.sleep(1);
        endless.interrupt();
        System.out.println("main: I have interrupted endless");
    }
}

  这段代码的输出为:

java多线程之“线程中断”的理解

java多线程之“线程中断”的理解

   虽然主线程试图中断endless,但它不为所动,还是把循环执行完了,正常退出。

  但不推荐线程进行如此任性的操作。当线程被发现被中断时,要不然把异常抛出(停止运行,throws InterruptedException);要不然推迟中断请求(继续进行操作),但需要把中断信息传递给调用该线程的上层调用者。对于第二种情况,来看一下下面的例子:

class Consumer extends Thread{
    public void run(){
        boolean interrupt = false;
        try {
            while (true){
                try {
                    consume(blockingQueue.take());
                    return;
                }catch (InterruptedException e){
                    interrupt = true;
                }
            }
        }finally {
            if (interrupt){
                Thread.currentThread().interrupt();
            }
            
        }
    }
}     

  线程Consumer会执着地等到消费了一个从阻塞队列中拿到的产品后,才结束执行,即使该线程的中断状态被设为true,且被blockingQueue.take()方法发现,并抛出InterruptedException后。但它在finally中将线程的中断状态重新置为了true,以保证调用Consumer的上层线程能知道该线程被中断过。

  但现在出现了一个问题是,为什么在finally中要调用interrupt()方法重新把线程中断状态置为true呢。讲道理如果blockingQueue.take()方法发现了中断,那就证明线程中断状态已经是true了啊。原因就在于这个blockingQueue的take()方法。在阻塞库方法,如Thread.sleep(), Object.wait(),以及这里的BlockingQueue.take()方法中(其实从BlockingQueue.take()的源码中可以看到,在take()方法中调用了Object.wait()),都及时地响应当前线程的中断请求,并且在响应时执行两个操作:一是清除中断状态,二是抛出InterruptedException。清除中断状态就是把中断状态重新置为false,这就解释了为什么在finally中要重新调用interrupt()方法。

 什么是恢复中断

  现在可以来解释一下什么叫恢复中断,我理解的恢复中断,并不是把线程从BLOCKED或WAITING等状态中解救出来(本来中断也不是这么个意思),而是把线程的中断状态变量重新设置为True。

写在后面:

看《Java并发编程实战》时看到第7章开头就看不明白,发现自己其实一直不懂中断到底是个什么东西,一直傻傻地认为中断和线程阻塞是一个意思。。。然后查了一些博客,并且把不懂的地方反复看了好几遍,才稍稍有些明白了。写博客其实就是捋捋自己的思路,顺便记录一下,如有错误恳请指出。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
4、jstack查看线程栈信息
1、介绍利用jps、top、jstack命令找到进程中耗时最大的线程,以及线程状态等等,同时最后还可以显示出死锁的线程查找:FoundoneJavaleveldeadlock即可1、jps获得进程号!(https://oscimg.oschina.net/oscnet/da00a309fa6
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
03.Android崩溃Crash库之ExceptionHandler分析
目录总结00.异常处理几个常用api01.UncaughtExceptionHandler02.Java线程处理异常分析03.Android中线程处理异常分析04.为何使用setDefaultUncaughtExceptionHandler前沿上一篇整体介绍了crash崩溃
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这