浅聊线程中断

2020-06-02 09:55:44 浏览数 (2)

在前面分析Condition的时候,被阻塞的线程在我关闭应用的时候,会抛出异常,这是因为阻塞的线程被其他线程中断了。其实在学习AQS的时候我们也说过线程中断,AQS中acquire方法会忽略线程中断。现在我们来了解一下什么叫线程中断

首先我们需要知道线程的thread.interrupt()方法是中断线程,但是线程中断并不是说明,线程被中止了,只是给线程标识一个中断状态,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。它不同于stop方法那样会中断一个正在运行的线程,但是现在已经不再推荐使用stop方法了(原因有很多,比如线程安全)。

01

在正式开始之前我们先了解一下线程的状态,这对于我们理解线程中断很有帮助。

在Thread类中存在一个枚举类State,它列举了线程的6种状态,NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING。这6种状态分别如下含义。

1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

3.阻塞(BLOCKED):表示线程阻塞于锁。

4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

6. 终止(TERMINATED):表示该线程已经执行完毕。

为什么说这六种状态呢?因为线程每一个状态对于是否响应中断是不一样的。线程在初始阶段和中止阶段是不会响应中断的,也就是说这两个状态是屏蔽中断的。我们来看一下例子:

代码语言:javascript复制
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1");
            }
        });
//        thread.start();
        System.out.println(thread.isInterrupted());
        thread.interrupt();
        System.out.println(thread.isInterrupted());
//        System.out.println(Thread.interrupted());

    }

我们新建的线程如果不启动,也就是初始阶段,调用interrupt方法前后,我们使用isInterrupted方法都会返回false.也就是说中断没有效果。但是当我们启动线程之后,情况就多了一些:

如果线程比主线程执行的快,中断前后仍然是返回false。

代码语言:javascript复制
false
1
false
或者
1
false
false

但是如果线程执行较慢,就会有这样的情况

代码语言:javascript复制
false
true
1
或者
false
1
true

这说明的什么呢?我个人觉得有如下两点,首先即使isInterrupted判断线程中断标识为true,线程仍然执行,也就是中断标识不影响线程的执行(不做额外控制的情况),然后就是在线程初始和终止阶段中断无效,但是运行状态是有效的(这就是为什么会为true的情况)。6种状态下的中断我们分析了三种,那么还有三种,我们先看一下阻塞状态:我们自定义一个线程,做死循环,启动两个线程,我们会发现一个运行,一个阻塞。此时我们中断是有效的。也就是说处于BLOCKED状态的线程,中断标志可以被修改。当我想到这里的时候,我还在想另外一个问题:Condition中的await方法是让线程进入阻塞还是等待,从名字上看他好像是让线程等待,而不是阻塞,我们进入源码看一下,方法描述上有这么一句话:Block until signalled or interrupted。可见确实是阻塞。线程阻塞和等待的区别,我们有必要去了解一下。我的理解就是阻塞是被动的,当你访问资源被其他线程占有的时候,你只能阻塞,但是当你被通知停一下,比如sleep方法,wait方法,你只能等待别人让你执行(唤醒)。

代码语言:javascript复制
  /*自定义线程类*/
    static class MyThread extends Thread {
        public synchronized static void doSomething() {
            while (true) {
                //do something
            }
        }
        @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();
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());
        System.out.println(thread2.isInterrupted());
        thread2.interrupt();
        System.out.println(thread2.isInterrupted());

    }

结果:

代码语言:javascript复制
RUNNABLE
BLOCKED
false
true

剩下WAITING/TIMED_WAITING这两种状态本质上是同一种状态,只不过TIMED_WAITING在等待一段时间后会自动释放自己,而WAITING则是无限期等待,需要其他线程调用notify方法释放自己。但是他们都是线程在运行的过程中由于缺少某些条件而被挂起在某个对象的等待队列上。当这些线程遇到中断操作的时候,会抛出一个InterruptedException异常,并清空中断标志位。

代码语言:javascript复制
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    try {
                        wait();
                    } catch (InterruptedException e) {

                        System.out.println("等待过程中,执行中断方法");
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
        System.out.println(thread.getState());
        System.out.println(thread.isInterrupted());

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


    }

执行结果,不一定是WAITING状态,也有可能是RUNNABLE,这个根据主线程和当前线程谁执行快慢有关。我们会发现中断方法执行之后输出false。存在的问题就是,捕获异常之后怎么处理,我在网上看到最多的就是在捕获之后加上Thread.currentThread().interrupt();方便栈中更高层的代码能知道中断,并且对中断作出响应。

代码语言:javascript复制
WAITING
false
java.lang.InterruptedException
Disconnected from the target VM, address: '127.0.0.1:53224', transport: 'socket'
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.study.java.manager.assets.sug.Quenue$1.run(Quenue.java:111)
    at java.lang.Thread.run(Thread.java:748)
等待过程中,执行中断方法
false

上面我们就说完了线程的6种状态,以及中断方法是否生效:

NEW和TERMINATED对于中断操作几乎是屏蔽的,RUNNABLE和BLOCKED类似,对于中断操作只是设置中断标志位并没有强制终止线程,对于线程的终止权利依然在程序手中。WAITING/TIMED_WAITING状态下的线程对于中断操作是敏感的,他们会抛出异常并清空中断标志位。

下面我们就看一下三个方法:interrupt,isInterrupted,interrupted。

02

interrupt方法是用来设置中断状态的,当我们调用该方法的时候,会将线程的标识为设为true。表明该线程执行过中断方法。中断方法是否生效就根据我们刚才谈到线程的状态情况来定了。我们调用sleep、wait等此类可中断(throw InterruptedException)方法时,一旦方法抛出InterruptedException,当前调用该方法的线程的中断状态就会被jvm自动清除了,就是说我们调用该线程的isInterrupted 方法时是返回false。如果你想保持中断状态,可以再次调用interrupt方法设置中断状态。这样做的原因是,java的中断并不是真正的中断线程,而只设置标志位(中断位)来通知用户。如果你捕获到中断异常,说明当前线程已经被中断,不需要继续保持中断位

就中断就会有判断是否中断,isInterrupted方法就是判断线程是否中断的方法,其底层是一个native方法,ClearInterrupted表示清除中断状态,而isInterrupted传入false表明其只查询线程中断状态,并不清除中断。

代码语言:javascript复制
   public boolean isInterrupted() {
        return isInterrupted(false);
    }

-------------
private native boolean isInterrupted(boolean ClearInterrupted);

interrupted方法比interrupt方法多了ed。但是它是清掉原来的状态位。interrupted是静态方法,返回的是当前线程的中断状态。例如,如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。

代码语言:javascript复制
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

-------------
private native boolean isInterrupted(boolean ClearInterrupted);

那么到这里关于线程中断的分析就结束了。有帮助不妨点个赞吧

0 人点赞