[并发J.U.C] 用例子理解线程中断

前言

本文主要集中在Java线程的中断的使用和操作上.
完整代码:代码

方法

关于中断的有三个方法都在java.lang.thread下,分别如下:

方法 描述
public boolean isInterrupted() 判断某个线程是否中断
public static boolean interrupted() 判断当前线程是否中断,与isInterrupted()的区别在于如果当前线程中断了,该方法在返回true后,会进行复位(即把中断标识符设置为false)
public void interrupt() 中断某个线程

再看一个例子之前先注意一点:

1.如果某个线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()方法会依旧返回false

例子1

通过该例子简单理解一下中断的操作

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Test1 {

    static CountDownLatch countdown = new CountDownLatch(1);
    //countdown 是为了保证mythread-1充分运行完
    
    public static void main(String[] args) {
        Thread thread = new Thread(new Runner1(), "mythread-1");
        thread.start();
        try {
            TimeUnit.NANOSECONDS.sleep(1);
            thread.interrupt();
            TimeUnit.NANOSECONDS.sleep(1);
            thread.interrupt();
            countdown.await(); 
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        printThreadInfo(Thread.currentThread());
        printThreadInfo(thread);
        System.out.println("main ends!");
    }
    
    static void printThreadInfo(Thread thread) {
        System.out.println(thread.getName() + ", interrupte :" + thread.isInterrupted());
    }
    
    static class Runner1 implements Runnable {
        public void run() {
            System.out.println("Thread-name:" + Thread.currentThread().getName() + ", interrupted:" + Thread.currentThread().isInterrupted());
            int i = 0;
            while(true) {
                if (Thread.interrupted()) {
                    System.out.println(Thread.currentThread().getName() + " receives interrupt signal in first loop");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " prints " + (i++) + " in first loop.");
            }
            System.out.println(Thread.currentThread().getName() + " does not stop!");
            printThreadInfo(Thread.currentThread());
            
            
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " receives interrupt signal in second loop");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " prints " + (i++) + " in second loop.");

            }
            printThreadInfo(Thread.currentThread());
            countdown.countDown();
        }
    }
}

首先定义了一个CountDownLatch为了保证让mythread-1充分运行后在运main线程await()后面的语句.

线程mythread-1有两个循环,第一个用Thread.interrupted()来判断当前线程是否中断,如果有中断,它会进行复位.第二个循环使用isInterrupted()来判断当前线程是否有中断.

主线程main中调用了两次interrupt()中断操作来告诉mythread-1.

我的电脑上运行结果如下:

Thread-name:mythread-1, interrupted:false
mythread-1 prints 0 in first loop.
mythread-1 prints 1 in first loop.
mythread-1 prints 2 in first loop.
mythread-1 prints 3 in first loop.
mythread-1 prints 4 in first loop.
mythread-1 prints 5 in first loop.
mythread-1 prints 6 in first loop.
mythread-1 prints 7 in first loop.
mythread-1 prints 8 in first loop.
mythread-1 prints 9 in first loop.
mythread-1 prints 10 in first loop.
mythread-1 prints 11 in first loop.
mythread-1 prints 12 in first loop.
mythread-1 prints 13 in first loop.
mythread-1 prints 14 in first loop.
mythread-1 prints 15 in first loop.
mythread-1 prints 16 in first loop.
mythread-1 prints 17 in first loop.
mythread-1 prints 18 in first loop.
mythread-1 prints 19 in first loop.
mythread-1 prints 20 in first loop.
mythread-1 prints 21 in first loop.
mythread-1 prints 22 in first loop.
mythread-1 prints 23 in first loop.
mythread-1 prints 24 in first loop.
mythread-1 prints 25 in first loop.
mythread-1 prints 26 in first loop.
mythread-1 prints 27 in first loop.
mythread-1 prints 28 in first loop.
mythread-1 prints 29 in first loop.
mythread-1 prints 30 in first loop.
mythread-1 prints 31 in first loop.
mythread-1 prints 32 in first loop.
mythread-1 prints 33 in first loop.
mythread-1 prints 34 in first loop.
mythread-1 prints 35 in first loop.
mythread-1 prints 36 in first loop.
mythread-1 prints 37 in first loop.
mythread-1 prints 38 in first loop.
mythread-1 prints 39 in first loop.
mythread-1 prints 40 in first loop.
mythread-1 prints 41 in first loop.
mythread-1 prints 42 in first loop.
mythread-1 prints 43 in first loop.
mythread-1 receives interrupt signal in first loop
mythread-1 does not stop!
mythread-1, interrupte :false
mythread-1 prints 44 in second loop.
mythread-1 prints 45 in second loop.
mythread-1 prints 46 in second loop.
mythread-1 prints 47 in second loop.
mythread-1 prints 48 in second loop.
mythread-1 prints 49 in second loop.
mythread-1 prints 50 in second loop.
mythread-1 prints 51 in second loop.
mythread-1 prints 52 in second loop.
mythread-1 prints 53 in second loop.
mythread-1 prints 54 in second loop.
mythread-1 prints 55 in second loop.
mythread-1 prints 56 in second loop.
mythread-1 prints 57 in second loop.
mythread-1 prints 58 in second loop.
mythread-1 prints 59 in second loop.
mythread-1 prints 60 in second loop.
mythread-1 prints 61 in second loop.
mythread-1 prints 62 in second loop.
mythread-1 prints 63 in second loop.
mythread-1 prints 64 in second loop.
mythread-1 prints 65 in second loop.
mythread-1 prints 66 in second loop.
mythread-1 prints 67 in second loop.
mythread-1 receives interrupt signal in second loop
mythread-1, interrupte :true
main, interrupte :false
mythread-1, interrupte :false
main ends!

从结果中可以看到主线程第一次调用thread.interrupt();的时候会mythread-1线程中的第一个循环利用Thread.interrupted()会检查到中断状态已经判断为true然后会立马复位为false,从跳出第一个循环后打印的中断状态信息就可以知道线程状态已经被复位为false了.

主线程第二次调用thread.interrupt();的时候会mythread-1线程中的第二个循环利用Thread.currentThread().isInterrupted()会检查到中断状态会判断为true,跳出循环后会发现这次并没有复位.
所以从这个例子中就可以明显区分interrupted()isInterrupted()了.

最后在主线程打印mythread-1的中断状态信息,返回的是false已经该线程已经处于终结状态了.

不过从这个例子中也可以看到几点疑惑:
1. 线程收到线程中断信息后并没有立刻停止线程,而是继续运行.
2.InterruptedException是什么?有什么用?

中断的理解

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作也就是该线程的中断状态.

下面一段话我觉得写得很好,摘录于线程中断

中断是一种协作机制。当一个线程中断另一个线程时,被中断的线程不一定要立即停止正在做的事
情。
相反,中断是礼貌地请求另一个线程在它愿意并且方便的时候停止它正在做的事情。有些方法,
例如 Thread.sleep(),很认真地对待这样的请求,但每个方法不是一定要对中断作出响应。
对于中断请求,不阻塞但是仍然要花较长时间执行的方法可以轮询中断状态,并在被中断的时候
提前返回。 您可以随意忽略中断请求,但是这样做的话会影响响应。

因为这样可以带来灵活性,用户可以根据自己的需求来决定是否进行响应.

当另一个线程通过调用 Thread.interrupt() 中断一个线程时,会出现以下两种情况之一
1. 那个线程在执行一个低级可中断阻塞方法,例如 Thread.sleep()、 Thread.join() 或 Object.wait(),那么它将取消阻塞并抛出 InterruptedException. 因为这些阻塞方法,由于无法预测什么时间会结束,因此需要一种中断机制来进行通知该线程的调用者,让调用者明白该线程已经发生中断并且提前返回.
注意:这些方法会在异常抛出的时候会清除当前线程的中断状态wait()方法的源码看一下就可以了

 The interrupted status of the current thread is cleared when this exception is thrown.

2. interrupt() 只是设置线程的中断状态.

例子2

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Test2 {
    
    public static void main(String[] args) {
        Object obj = new Object();
        Thread thread = new Thread(new Runner2(obj), "mythread-1");
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1);
            thread.interrupt();
            TimeUnit.MILLISECONDS.sleep(1);
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    static void printThreadInfo(String info, Thread thread) {
        System.out.println(info + thread.getName() + ", interrupte :" + thread.isInterrupted());
    }
    
    static class Runner2 implements Runnable {
        Object obj;
        public Runner2(Object obj) {
            this.obj = obj;
        }
        public void run() {
            synchronized(obj) {
                try {
                    System.out.println(Thread.currentThread().getName() + " before waits");
                    obj.wait();
                    System.out.println(Thread.currentThread().getName() + " after waits");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    printThreadInfo("in wait catch ", Thread.currentThread());
                } 
            }
            try {
                System.out.println(Thread.currentThread().getName() + " before sleep");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " after sleep");
            } catch (InterruptedException e) {
                e.printStackTrace();
                printThreadInfo("in sleep catch ", Thread.currentThread());
            } 
            System.out.println("end!");
        }
    }
}

例子上面为什么wait()方法中为什么要加一个锁,这里有一篇文章可以看一下java 为什么wait(),notify(),notifyAll()必须在同步方法/代码块中调用?

结果如下: 正如我们预想的一样,中断会使这些方法从阻塞状态中提前返回,并且在抛出异常是清除了中断状态.

mythread-1 before waits
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.understand.interrupt.Test2$Runner2.run(Test2.java:35)
    at java.lang.Thread.run(Thread.java:745)
in wait catch mythread-1, interrupte :false
mythread-1 before sleep
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.understand.interrupt.Test2$Runner2.run(Test2.java:44)
    at java.lang.Thread.run(Thread.java:745)
in sleep catch mythread-1, interrupte :false
end!

但是上面的例子有个问题就是如果不去查看Log信息,主线程也就是调用者根本就不知道mythread-1线程发生过中断.

所以接下来我们看看如果处理InterruptedException异常.

处理InterruptedException异常

抛出一个InterruptedException异常,说明调用的方法是一个阻塞方法,因此自身来说也是一个阻塞方法.

1.最简单的一种方式是直接继续抛出异常给上层方法.如下所示:

public static void mySleep(long timeout) throws InterruptedException {
        System.out.println("i want to sleep.");
        TimeUnit.MILLISECONDS.sleep(timeout);
        System.out.println("wake up");
}

2.第二种方式是捕捉该异常,做一些清理工作,比如关闭流之类的,然后再继续抛出异常给上层方法.

public static void mySleep1(long timeout) throws InterruptedException {
        System.out.println("i want to sleep.");
        try {
            TimeUnit.MILLISECONDS.sleep(timeout);
        } catch (InterruptedException e) {
            //clear somthing
            throw e;
        }
        System.out.println("wake up");
}

3. 第三种方式是你调用的方法中不让抛出异常,比如例子2中的线程中的run()方法,因此此时可以捕捉异常后保留中断状态让调用者知道它状态,又由于在阻塞方法抛出异常的时候会清空中断状态,所以此时可以在catch语句中调用Thread.currentThread().interrupt()方法设置一下中断状态.如下所示:

static class Runner implements Runnable {
        public void run() {
            try {
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace(); 
                Thread.currentThread().interrupt();
            }
        }
}

参考

1. https://www.ibm.com/developerworks/cn/java/j-jtp05236.html
2. https://blog.csdn.net/haluoluo211/article/details/49558155
3. Java并发编程的艺术

你可能感兴趣的:([并发J.U.C] 用例子理解线程中断)