Java多线程9:中断机制

Wesley13
• 阅读 397

一、概述

  之前讲解Thread类中方法的时候,interrupt()、interrupted()、isInterrupted()三个方法没有讲得很清楚,只是提了一下。现在把这三个方法同一放到这里来讲,因为这三个方法都涉及到多线程的一个知识点----中断机制。

  Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。有个例子举个蛮好,就像父母叮嘱出门在外的子女要注意身体一样,父母说了,但是子女是否注意身体、如何注意身体,还是要看自己。

  中断机制也是一样的,每个线程对象里都有一个标识位表示是否有中断请求(当然JDK的源码是看不到这个标识位的,是虚拟机线程实现层面的),代表着是否有中断请求。

二、三个中断有关的方法

  1、interrupt()方法

 1 /**
 2      * Interrupts this thread.
 3      *
 4      * <p> Unless the current thread is interrupting itself, which is
 5      * always permitted, the {@link #checkAccess() checkAccess} method
 6      * of this thread is invoked, which may cause a {@link
 7      * SecurityException} to be thrown.
 8      *
 9      * <p> If this thread is blocked in an invocation of the {@link
10      * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
11      * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
12      * class, or of the {@link #join()}, {@link #join(long)}, {@link
13      * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
14      * methods of this class, then its interrupt status will be cleared and it
15      * will receive an {@link InterruptedException}.
16      *
17      * <p> If this thread is blocked in an I/O operation upon an {@link
18      * java.nio.channels.InterruptibleChannel InterruptibleChannel}
19      * then the channel will be closed, the thread's interrupt
20      * status will be set, and the thread will receive a {@link
21      * java.nio.channels.ClosedByInterruptException}.
22      *
23      * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
24      * then the thread's interrupt status will be set and it will return
25      * immediately from the selection operation, possibly with a non-zero
26      * value, just as if the selector's {@link
27      * java.nio.channels.Selector#wakeup wakeup} method were invoked.
28      *
29      * <p> If none of the previous conditions hold then this thread's interrupt
30      * status will be set. </p>
31      *
32      * <p> Interrupting a thread that is not alive need not have any effect.
33      *
34      * @throws  SecurityException
35      *          if the current thread cannot modify this thread
36      *
37      * @revised 6.0
38      * @spec JSR-51
39      */
40     public void interrupt() {
41         if (this != Thread.currentThread())
42             checkAccess();
43 
44         synchronized (blockerLock) {
45             Interruptible b = blocker;
46             if (b != null) {
47                 interrupt0();           // Just to set the interrupt flag
48                 b.interrupt(this);
49                 return;
50             }
51         }
52         interrupt0();
53     }

1 /* Some private helper methods */
2     private native void setPriority0(int newPriority);
3     private native void stop0(Object o);
4     private native void suspend0();
5     private native void resume0();
6     private native void interrupt0();
7     private native void setNativeName(String name);

  ①:看一下第47行,interrupt()方法的作用只是设置中断标识位(Just to set the interrupt flag),并不会强制要求线程必须进行处理。再看一下第32行,interrupt()方法作用的线程是处于不是终止状态或新建状态的线程(Interrupting a thread that is not alive need not have any effect.)。

  关于线程是否是alive的判断(A thread is alive if it has been started and has not yet died.)

  ②:看一下第29-30行,只有当那三种情况都不成立时,interrupt()方法才会设置线程的中断标识位(If none of the previous conditions hold then this thread's interrupt status will be set.)。这里介绍第一种,当调用Object的wait()/wait(long)/wait(long, int)方法,或是调用线程的join()/join(long)/join(long, int)/sleep(long)/sleep(long, int)方法, 那么interrupt()方法作用的线程的中断标识位会被清除并抛出InterruptedException异常。其余两种情况自己参看注释。

  ③:看一下第二块代码的第6行interrupt0()方法(设置中断标识位),这是一个被native修饰的方法,很明显这是一个本地方法,是Java虚拟机实现的。

  2、isInterrupted()

  该方法的作用就是测试线程是否已经中断,且线程的中断标识位并不受该方法的影响。

 1 /**
 2      * Tests whether this thread has been interrupted.  The <i>interrupted
 3      * status</i> of the thread is unaffected by this method.
 4      *
 5      * <p>A thread interruption ignored because a thread was not alive
 6      * at the time of the interrupt will be reflected by this method
 7      * returning false.
 8      *
 9      * @return  <code>true</code> if this thread has been interrupted;
10      *          <code>false</code> otherwise.
11      * @see     #interrupted()
12      * @revised 6.0
13      */
14     public boolean isInterrupted() {
15         return isInterrupted(false);
16     }
17 
18     /**
19      * Tests if some Thread has been interrupted.  The interrupted state
20      * is reset or not based on the value of ClearInterrupted that is
21      * passed.
22      */
23     private native boolean isInterrupted(boolean ClearInterrupted);

  ①:注意一下第5-7行,若是调用isInterrupted()方法时,当前已经调用interrupt()方法的线程was not alive,方法会返回false,而不是true。

  ②:最终调用的是isInterrupted(boolean ClearInterrupted),这个方法是一个native的,看得出也是Java虚拟机实现的。方法的参数ClearInterrupted,顾名思义,清除中断标识位,这里传递false,明显就是不清除。

  举例:验证一下①中情况

public class Thread01 extends Thread{

    @Override
    public void run() {

    }
}

  测试一下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread01 thread01 = new Thread01();
        thread01.start();
        thread01.interrupt();
        Thread.sleep(10);
        System.out.println(thread01.isInterrupted());
        System.out.println(thread01.isInterrupted());
        System.out.println(thread01.isInterrupted());
    }
}

  结果:

false
false
false

  可以看到,虽然调用了interrupt()方法,但是在调用isInterrupted()方法的时候,线程已经执行完了,所以返回的是false。

  看一下反例,调用interrupt()方法,在线程没执行完的时候调用isInterrupted()方法

public class Thread01 extends Thread{

    @Override
    public void run() {
        for(int i = 0; i < 50000; i++) {

        }
    }
}

  测试一下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread01 thread01 = new Thread01();
        thread01.start();
        thread01.interrupt();
        System.out.println(thread01.isInterrupted());
        System.out.println(thread01.isInterrupted());
        System.out.println(thread01.isInterrupted());
    }
}

  结果:

true
true
true

  3、interrupted()

  该方法的作用就是测试当前线程(currentThread)是否已经中断,且线程的中断标识位会被清除,换句话说,连续成功调用两次该方法,第二次的返回值一定是false。注意该方法是一个静态方法。

 1 /**
 2      * Tests whether the current thread has been interrupted.  The
 3      * <i>interrupted status</i> of the thread is cleared by this method.  In
 4      * other words, if this method were to be called twice in succession, the
 5      * second call would return false (unless the current thread were
 6      * interrupted again, after the first call had cleared its interrupted
 7      * status and before the second call had examined it).
 8      *
 9      * <p>A thread interruption ignored because a thread was not alive
10      * at the time of the interrupt will be reflected by this method
11      * returning false.
12      *
13      * @return  <code>true</code> if the current thread has been interrupted;
14      *          <code>false</code> otherwise.
15      * @see #isInterrupted()
16      * @revised 6.0
17      */
18     public static boolean interrupted() {
19         return currentThread().isInterrupted(true);
20     }

1 private native boolean isInterrupted(boolean ClearInterrupted);

  ①:注意一下第9-11行,若是调用interrupted()方法时,当前已经调用interrupt()方法的线程was not alive,方法会返回false,而不是true。

  ②:最终调用的是isInterrupted(boolean ClearInterrupted),这个方法是一个native的,看得出也是Java虚拟机实现的。方法的参数ClearInterrupted,顾名思义,清除中断标识位,这里传递true,明显就是清除线程的中断标识位。

  此外,JDK API中有些类的方法也可能会调用中断,比如FutureTask的cancel,如果传入true则会在正在运行的异步任务上调用interrupt()方法,又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt()方法。这些场景下只要代码没有对中断作出响应,那么任务将一直执行下去。

  举例1:说明interrupted()方法测试的是当前线程是否被中断

public class Thread01 extends Thread{

    private List<Integer> list = new ArrayList<>();
    @Override
    public void run() {
        System.out.println("开始执行run方法");
        for(int i = 0; i < 50000; i++) {
            list.add(i);
        }
        System.out.println("执行run方法结束");
        
    }
}

  测试:

 1 public class Test {
 2     public static void main(String[] args) throws InterruptedException {
 3         Thread01 thread01 = new Thread01();
 4         thread01.start();
 5         Thread.sleep(5);
 6         thread01.interrupt();
 7         System.out.println(Thread.interrupted());
 8         System.out.println(thread01.isInterrupted());
 9         System.out.println(thread01.isInterrupted());
10     }
11 }

  结果:

开始执行run方法
false
true
true
执行run方法结束

  说明:由8行和9行结果来看,thread01确实是在执行期间被设置了中断标识位,但是第7行结果并没有返回true,这是因为Thread.interrupted()方法测试的当前线程是否处于中断状态,其当前线程是main线程,而main线程并没有处于中断状态,所以返回false。

  举例2:若是run方法之行结束了,再来测试thread01是否处于中断状态,那么结果是什么

public class Thread01 extends Thread{
    @Override
    public void run() {
        System.out.println("开始执行run方法");
        System.out.println("执行run方法结束");
    }
}

  测试:sleep方法确保thread01执行完了在测试其是否处于中断状态

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread01 thread01 = new Thread01();
        thread01.start();
        thread01.interrupt();
        Thread.sleep(50);
        System.out.println(Thread.interrupted());
        System.out.println(thread01.isInterrupted());
        System.out.println(thread01.isInterrupted());
    }
}

  结果:

开始执行run方法
执行run方法结束
false
false
false

  说明:可以看到,即使thread01被设置了中断标识位,但若是线程执行完成了再来测试其是否处于中断状态,那么一定会返回false。

  举例3:中断main线程,给main线程设置中断标识位

public class Thread01 extends Thread{

    private List<Integer> list = new ArrayList<>();
    @Override
    public void run() {
        System.out.println("开始执行run方法");
        for(int i = 0; i < 50000; i++) {
            list.add(i);
        }
        System.out.println("执行run方法结束");
    }
}

  测试:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread01 thread01 = new Thread01();
        thread01.start();
        thread01.interrupt();
        Thread.sleep(5);
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());
        System.out.println(Thread.interrupted());
        System.out.println(thread01.isInterrupted());
        System.out.println(thread01.isInterrupted());
    }
}

  结果:

开始执行run方法
true
false
true
true
执行run方法结束

三、中断处理时机

  这其实是一个很宽泛的、没有标注答案的话题。显然,作为一种协作机制,不会强求被中断的线程一定要在某个点进行中断处理。实际上,被中断线程只需要在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理。"合适的时间点"就和业务逻辑密切相关了。

  处理时机决定着程序的效率和响应的灵敏度。频繁的检查中断可能会导致程序执行效率低下,较少的检查则可能导致中断请求得不到及时响应。在实际场景中,如果性能指标比较关键,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性

四、线程中断举例

  示例1:线程被设置了中断标识位且没有抛出异常:InterruptedException

public class Thread01 extends Thread{

    @Override
    public void run() {

        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName() + "被中断了");
                break;
            }else{
                System.out.println(Thread.currentThread().getName() + "在运行中");
            }
        }

    }
}

  测试:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread01 thread01 = new Thread01();
        thread01.start();
        Thread.sleep(1000);
        thread01.interrupt();
    }
}

  结果:

.......
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0在运行中
Thread-0被中断了

  代码分为以下几步:

  1、main函数起一个thread01线程

  2、main函数1秒钟之后给t线程打一个中断标识位,表示thread01线程要中断

  3、thread01线程无限轮询自己的中断标识位,中断了则打印、退出,否则一直运行

  从控制台上打印的语句看到,1秒钟中断后,就停止了。那这种场景就是前面说的"频繁地检查",导致程序效率低下;那如果不频繁地检查呢,比如在while中的else分支中加上Thread.sleep(500),表示500ms即0.5s检查一次,那这种场景就是前面说的"中断得不到及时的响应"。

  其实这个例子中,thread01线程完全可以不用去管这个中断标识位的,不去检查就好了,只管做自己的事情,这说明中断标识位设不设置是别人的事情,处不处理是我自己的事情,没有强制要求必须处理中断。按照这个例子理解就是,main线程中给thread01线程设置了中断标识位,但是thread01线程处不处理就是它自己的事情了。

   但是,那些会抛出InterruptedException的方法要除外。像sleep、wait、notify、join,这些方法遇到中断必须有对应的措施,可以直接在catch块中处理,也可以抛给上一层。这些方法之所以会抛出InterruptedException就是由于Java虚拟机在实现这些方法的时候,本身就有某种机制在判断中断标识位,如果中断了,就抛出一个InterruptedException。

  示例2:若线程被设置了中断标识位,调用sleep方法时会抛出异常

public class Thread01 extends Thread{

    @Override
    public void run() {

        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName() + "被中断了");
                break;
            }else{
                try {
                    Thread.sleep(400);
                    System.out.println(Thread.currentThread().getName() + "运行中");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("sleep方法抛出了异常");
                    break;
                }
            }
        }

    }
}

  测试:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread01 thread01 = new Thread01();
        thread01.start();
        Thread.sleep(1000);
        thread01.interrupt();
    }
}

  结果:

Java多线程9:中断机制

参考资料:

Java多线程17:中断机制

Thread中interrupted()方法和isInterrupted()方法区别总结

点赞
收藏
评论区
推荐文章
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年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
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年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
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之前把这