专栏首页健程之道Java面试-interrupt

Java面试-interrupt

我们都知道,Java中停止一个线程不能用stop,因为stop会瞬间强行停止一个线程,且该线程持有的锁并不能释放。大家多习惯于用interrupt,那么使用它又有什么需要注意的呢?

interrupt相关的方法

Java中和interrupt相关的方法有三个

public boolean isInterrupted()

public void interrupt()

public static boolean interrupted()

boolean isInterrupted()

每个线程都一个状态位用于标识当前线程对象是否是中断状态。isInterrupted主要用于判断当前线程对象的中断标志位是否被标记了,如果被标记了则返回true,表示当前已经被中断,否则返回false。我们也可以看看它的实现源码:

    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    private native boolean isInterrupted(boolean ClearInterrupted);

底层调用的native方法isInterrupted,传入一个boolean类型的参数,用于指定调用该方法之后是否需要清除该线程的中断标识位。从这里我们也可以看出来,调用isInterrupted()并不会清除线程的中断标识位。

void interrupt()

interrupt()用于设置当前线程对象的中断标识位,其源码为:

    public void interrupt() {
        // 检查当前线程是否有权限修改目标线程,如果没有,则会抛出异常SecurityException
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

blockerLockblocker都和阻塞IO时产生的中断相关,因此推测interrupt()需要当阻塞IO操作执行完之后,才可以执行。

interrupt()其实只是改变了一个标志位,对于线程本身的状态并没有影响。

boolean interrupted()

该方法是一个静态的方法,用于返回当前线程是否被中断,其源码是:

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

需要注意的是:该方法调用结束的时候会清空中断标识位

线程的状态与中断的关系

我们知道,Java中的线程一共6种状态,分别是NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED(Thread类中有一个State枚举类型列举了线程的所有状态)。下面我们就将把线程分别置于上述的不同种状态,然后看看中断操作对它们的影响。

NEW和TERMINATED

NEW状态表示线程还未调用start()方法,TERMINATED状态表示线程已经运行终止。

这两个状态下调用中断方法来中断线程的时候,Java认为毫无意义,所以并不会设置线程的中断标识位。例如:

NEW状态:

public static void main(String[] args) {
    Thread thread = new Thread();
    System.out.println(thread.getState());

    System.out.println(thread.isInterrupted());
    thread.interrupt();
    System.out.println(thread.isInterrupted());
}

输出结果:

NEW
false
false

TERMINATED状态:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread();
    // 启动线程
    thread.start();
    // 等待线程结束
    thread.join();
    System.out.println(thread.getState());

    System.out.println(thread.isInterrupted());
    thread.interrupt();
    System.out.println(thread.isInterrupted());
}

输出结果:

TERMINATED
false
false

从上述的两个例子来看,处于NEWTERMINATED状态的线程,对于中断是屏蔽的,也就是说中断操作对这两种状态下的线程是无效的。

RUNNABLE

处于RUNNABLE状态的线程,当中断线程后,会修改其中断标志位,但并不会影响线程本身。例如:

/**
 * 自定义线程类
 */
public class MyThread extends Thread{

    @Override
    public void run(){
        while(true){
            // 什么都不做,就是空转
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
        System.out.println(thread.getState());

        System.out.println(thread.isInterrupted());
        thread.interrupt();
        System.out.println(thread.isInterrupted());

        System.out.println(thread.getState());
    }
}

结果为:

RUNNABLE
false
true
RUNNABLE

中断标志位确实被改变了,但线程依旧继续运行。那我们调用interrupt()方法的意义在哪儿?

其实Java是将中断线程的权利交给了我们自己的程序,通过中断标志位,我们的程序可以通过boolean isInterrupted()方法来判断当前线程是否中断,从而决定之后的操作。

我们可以在此基础上,保证执行任务的原子性。例如修改MyThread类的方法:

/**
 * 自定义线程类
 */
public class MyThread extends Thread{

    @Override
    public void run(){
        while(true){
            if (this.isInterrupted()){
                System.out.println("exit MyThread");
                break;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        System.out.println(thread.getState());

        System.out.println(thread.isInterrupted());
        thread.interrupt();
        System.out.println(thread.isInterrupted());

        thread.join();
        System.out.println(thread.getState());
    }
}

结果为:

RUNNABLE
false
true
exit MyThread
TERMINATED

BLOCKED

当线程处于BLOCKED状态,说明该线程由于竞争某个对象的锁失败而被挂在了该对象的阻塞队列上了。

那么此时发起中断操作不会对该线程产生任何影响,依然只是设置中断标志位。例如:

/**
 * 自定义线程类
 */
public class MyThread extends Thread{

    public synchronized static void doSomething(){
        while(true){
            // 空转
        }
    }
    @Override
    public void run(){
        doSomething();
    }

    public static void main(String[] args) throws InterruptedException {
        // 启动两个线程
        Thread thread1 = new MyThread();
        thread1.start();
        Thread thread2 = new MyThread();
        thread2.start();

        Thread.sleep(1000);
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());

        System.out.println(thread2.isInterrupted());
        thread2.interrupt();
        System.out.println(thread2.isInterrupted());
        System.out.println(thread2.getState());
    }
}

结果为:

RUNNABLE
BLOCKED
false
true
BLOCKED

thread2处于BLOCKED状态,执行中断操作之后,该线程仍然处于BLOCKED状态,但是中断标志位却已被修改。

这种状态下的线程和处于RUNNABLE状态下的线程是类似的,给了我们程序更大的灵活性去判断和处理中断。

WAITING/TIMED_WAITING

这两种状态本质上是同一种状态,只不过TIMED_WAITING在等待一段时间后会自动释放自己,而WAITING则是无限期等待,需要其他线程调用类似notify方法释放自己。但是他们都是线程在运行的过程中由于缺少某些条件而被挂起在某个对象的等待队列上。

当这些线程遇到中断操作的时候,会抛出一个InterruptedException异常,并清空中断标志位。例如:

/**
 * 自定义线程类
 */
public class MyThread extends Thread{

    @Override
    public void run(){
        synchronized (this){
            try {
                wait();
            } catch (InterruptedException e) {
                System.out.println("catch InterruptedException");

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();

        Thread.sleep(1000);
        System.out.println(thread.getState());

        System.out.println(thread.isInterrupted());
        thread.interrupt();
        Thread.sleep(1000);
        System.out.println(thread.isInterrupted());
    }
}

结果为:

WAITING
false
catch InterruptedException
false

从运行结果看,当线程启动之后就被挂起到该线程对象的等待队列上,然后我们调用interrupt()方法对该线程进行中断,输出了我们在catch中的输出语句,显然是捕获了InterruptedException异常,接着就看到该线程的中断标志位被清空。

因此我们要么就在catch语句中结束线程,否则就在catch语句中加上this.interrupt();,再次设置标志位,这样也方便在之后的逻辑或者其他地方继续判断。

总结

我们介绍了线程在不同状态下对于中断请求的反应:

  1. NEWTERMINATED对于中断操作几乎是屏蔽的。
  2. RUNNABLEBLOCKED类似,对于中断操作只是设置中断标志位并没有强制终止线程,对于线程的终止权利依然在程序手中。
  3. WAITINGTIMED_WAITING状态下的线程对于中断操作是敏感的,他们会抛出异常并清空中断标志位。

本文分享自微信公众号 - 健程之道(JianJianCoder)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java中Synchronized的优化原理

    我们知道,从 JDK1.6 开始,Java 对 Synchronized 同步锁做了充分的优化,甚至在某些场景下,它的性能已经超越了 Lock 同步锁。那么就让...

    健程之道
  • Java中的ThreadLocal

    关于 ThreadLocal,我们经常用它来解决多线程并发问题,那它究竟是如何做到的?今天就让我们来好好看一下。

    健程之道
  • ThreadLocal的进化——TransmittableThreadLocal

    上一篇文章中,我们谈到了 InheritableThreadLocal,它解决了 ThreadLocal 针对父子线程无法共享上下文的问题。但我们可能听说过阿里...

    健程之道
  • Java多线程实战:多线程方法详解

    从结果可知:Mythread的构造方法是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是线程自动调用的

    用户4143945
  • Java多线程——多线程方法详解

    从结果可知:Mythread的构造方法是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是线程自动调用的

    用代码征服天下
  • 机器学习期望最大算法:实例解析

    交流思想,注重分析,更注重通过实例让您通俗易懂。包含但不限于:经典算法,机器学习,深度学习,LeetCode 题解,Kaggle 实战。期待您的到来! 01 —...

    double
  • 深入理解join方法的实现原理

    用户2141593
  • 深入理解join方法的实现原理

    有几个方法都可以做到,这里主要是套路最简单的使用join方法,如何解决。 首先看join方法的API 这是随手百度的 : join()等待线程结束。

    矿泉水
  • TAF 入门源码学习总结

    TAF 是分布式基于 epoll 的多线程非阻塞的高性能且支持同步、异步、单向调用 RPC 框架。框架将网络线程和业务线程隔离,并通过队列和管道实现网络线程和业...

    serena
  • 等一等,你的多线程可别再乱 join 了。

    如果你在网上搜索“Python 多线程”,那么你会看到很多文章里面用到了一个关键词,叫做.join()。但是很多人的代码里面都在乱用 join(),例如:

    青南

扫码关注云+社区

领取腾讯云代金券