Java并发08:Thread的基本方法(5)-interrupt()、isInterrupted()

本章主要对Java中Thread类的基本方法进行学习。

1.序言
Thread类作为线程的基类,提供了一系列方法,主要有:

Thread.sleep(long):强制线程睡眠一段时间。
Thread.activeCount():获取当前程序中存活的线程数。
thread.start():启动一个线程。
Thread.currentThread():获取当前正在运行的线程。
thread.getThreadGroup():获取线程所在线程组。
thread.getName():获取线程的名字。
thread.getPriority():获取线程的优先级。
thread.setName(name):设置线程的名字。
thread.setPriority(priority):设置线程的优先级。
thread.isAlive():判断线程是否还存活着。
thread.isDaemon():判断线程是否是守护线程。
thread.setDaemon(true):将指定线程设置为守护线程。
thread.join():在当前线程中加入指定线程,使得这个指定线程等待当前线程,并在当前线程结束前结束。
thread.yield():使得当前线程退让出CPU资源,把CPU调度机会分配给同样线程优先级的线程。
thread.interrupt():使得指定线程中断阻塞状态,并将阻塞标志位置为true。
object.wai()、object.notify()、object.notifyAll():Object类提供的线程等待和线程唤醒方法。
为了便于阅读,将以上所有方法,放在5篇文章中进行学习。

本章主要学习绿色字体标记的方法,其他方法请参加其他章节。

2.interrupt()

 /**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     * ...
     */
    public void interrupt() {
        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();
    }

说明:

  • 除非是线程自己interrupt()自己,否则checkAccess()方法都会被调用,并可能抛出一个SecurityException异常。
  • 如果当前线程处于blocked阻塞(因为调用wait、sleep和join造成的)状态时被interrupt了,那么[中断标志位]将被清除并且收到一个InterruptedException异常
  • 如果当前线程处于blocked阻塞(因为NIO的InterruptibleChannel进行的I/O操作造成的)状态时被interrupt了,则会关闭channel,[中断标志位]将会被置为true,并且当前线程会收到一个ClosedByInterruptException异常。
  • 如果当前线程处于blocked阻塞(因为NIO的Selector造成的)状态时被interrupt了,那么[中断标志位]将被置为true,然后当前线程会立即从选择器区域返回并返回值(可能为非零的值)。

如果前面的情况都没有发生,则线程会将[中断标志位]将被置为true。


更加易懂的说法(不包括NIO部分):

  • interrupt()方法并不是中断线程,而是中断阻塞状态或者将线程的[中断标志位]置为true
  • 对于未阻塞的线程,interrupt()只是造成[中断标志位]=rue,线程本身运行状态不受影响。
  • 对于阻塞的线程,interrupt()会中断阻塞状态,使其转换成非阻塞状态,并清除[中断标志位]
  • 造成阻塞状态的情况有:sleep()、wait()和join()。
  • 阻塞状态的线程被中断时,只是中断了阻塞状态,即sleep()、wait()和join(),线程本身还在继续运行。

3.实例代码与结果
为了更加深刻的学习interrupt()方法,下面通过5个例子来进行说明。

3.1.非阻塞线程的interrupt()
下面的代码,展示了非阻塞线程的interrupt():

//interrupt()并不是中断线程
//interrupt():中断线程的阻塞(sleep/join/wait)状态,或将 中断标志位 置为 true
//所以如果是普通的运行中的线程,流程并不会受到影响
LOGGER.info("===========interrupt()并不是中断线程,只是将线程的 中断标志位 置为true");
new Thread(() -> {
    Thread thread = Thread.currentThread();
    for (int i = 0; i < 5; i++) {
        LOGGER.info("线程[" + thread.getName() + "]: i = " + i + ",isInterrupted = " + thread.isInterrupted());
        if (i == 3) {
            thread.interrupt();
        }
    }
    LOGGER.info("线程[" + Thread.currentThread().getName() + "]: 停止运行" + ",isInterrupted = " + thread.isInterrupted());
}).start();

运行结果:

2020-03-12 15:10:42 INFO  ThreadInterruptDemo:23 - ===========interrupt()并不是中断线程,只是将线程的 中断标志位 置为true
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:27 - 线程[Thread-0]: i = 0,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:27 - 线程[Thread-0]: i = 1,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:27 - 线程[Thread-0]: i = 2,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:27 - 线程[Thread-0]: i = 3,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:27 - 线程[Thread-0]: i = 4,isInterrupted = true
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:32 - 线程[Thread-0]: 停止运行,isInterrupted = 

分析:在i=3的时候进行了线程中断。所以i=4时,isInterrupted = true,但是程序还在继续运行。

结论:对于未阻塞的线程,interrupt()只是造成[中断标志位]=true,线程本身运行状态不受影响。

3.2.通过interrupt()与isInterrupted()控制for循环的运行状态(非阻塞线程)
下面的例子展示了如何通过interrupt()与isInterrupted()实现线程中for循环的中途跳出。

//interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环
Thread.sleep(200);
System.out.println();
LOGGER.info("===========interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环");
new Thread(() -> {
    Thread thread = Thread.currentThread();
    for (int i = 0; i < 5 && !thread.isInterrupted(); i++) {
        LOGGER.info("线程[" + thread.getName() + "] is running, i = " + i + ",isInterrupted = " + thread.isInterrupted());
        if (i == 3) {
            thread.interrupt();
            LOGGER.info("线程[" + thread.getName() + "] is isInterrupted, i = " + i + ", isInterrupted = " + thread.isInterrupted());
        }
    }
    LOGGER.info("线程[" + Thread.currentThread().getName() + "] 停止运行");
}).start();

运行结果:

2020-03-12 15:10:42 INFO  ThreadInterruptDemo:38 - ===========interrupt()、isInterrupted()可以结合,控制线程中的for(无阻塞状态)循环
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 0,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 1,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 2,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:42 - 线程[Thread-1] is running, i = 3,isInterrupted = false
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:45 - 线程[Thread-1] is isInterrupted, i = 3, isInterrupted = true
2020-03-12 15:10:42 INFO  ThreadInterruptDemo:48 - 线程[Thread-1] 停止运行

分析:其实就是利用了isInterrupted()的true和false状态。

备注:前提条件是当前线程时非阻塞的。

3.3.通过interrupt()与isInterrupted()控制while循环的运行状态(非阻塞线程)
下面的代码展示了如何通过interrupt()与isInterrupted()控制非阻塞线程中的无限while循环。

//interrupt()、isInterrupted()可以结合,控制线程中的while(无阻塞状态)循环
Thread.sleep(200);
System.out.println();
LOGGER.info("在无阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()");
Thread thread1 = new Thread(() -> {
    //如果当前线程没被中断,则一直进行
    while (!Thread.currentThread().isInterrupted()) {
        LOGGER.info("线程[" + Thread.currentThread().getName() + "]正在运行...");
    }
    LOGGER.info("线程[" + Thread.currentThread().getName() + "]停止运行");
});
thread1.start();
Thread.sleep(10);
thread1.interrupt();

运行结果:

2018-03-12 15:10:42 INFO ThreadInterruptDemo:54 - 在无阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()
2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行...
2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行...
2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行...
2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行...
2018-03-12 15:10:42 INFO ThreadInterruptDemo:58 - 线程[Thread-2]正在运行...
...
2018-03-12 15:10:42 INFO ThreadInterruptDemo:60 - 线程[Thread-2]停止运行

分析:

在有阻塞的线程中,调用interrupt方法,会中断当前的阻塞状态。如本例子中,线程本来应该睡眠5秒钟打印一次信息,但是因为interrupt方法直接中断了本次睡眠,然后进入到下次循环中。
在有阻塞的线程中,调用interrupt方法,会抛出InterruptedException异常。
在有阻塞的线程中,调用interrupt方法,会清空[中断标志位],导致程序一直运行下去。
3.5.阻塞线程(sleep/wait/joni)中的while循环应用interrupt()和isInterrupted()的正确姿势
把上面的代码修改一下,将try-catch提取至最外层,并将错误信息打印屏蔽,形成如下代码:

//中断有阻塞状态(sleep/wait/joni)的线程,会产生一个InterruptedException异常,并将中断标志位清空,所以不会结束线程
//所以在有阻塞状态(sleep/wait/joni)的while循环,除了应用interrupt()和isInterrupted()的结合外,还需要在外层catch这个异常,才能够停止线程
Thread.sleep(200);
System.out.println();
LOGGER.info("在有阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()+catch");
Thread thread3 = new Thread(() -> {
    try {
        //如果当前线程没被中断,则一直进行
        while (!Thread.currentThread().isInterrupted()) {
            LOGGER.info("线程[" + Thread.currentThread().getName() + "]正在运行...");
            Thread.sleep(5000);
        }
    } catch (InterruptedException e) {
        LOGGER.info("线程[" + Thread.currentThread().getName() + "]停止运行");
        //没有必要打印错误,因为这是故意让程序产生的错误
        //e.printStackTrace();
    }
});
thread3.start();
Thread.sleep(2000);
thread3.interrupt();

运行结果:

2018-03-12 15:52:40 INFO  ThreadInterruptDemo:91 - 在有阻塞状态(sleep/wait/joni)的while循环中应用interrupt()和isInterrupted()+catch
2018-03-12 15:52:40 INFO  ThreadInterruptDemo:96 - 线程[Thread-0]正在运行...
2018-03-12 15:52:42 INFO  ThreadInterruptDemo:100 - 线程[Thread-0]停止运行

使用interrupt()中断阻塞线程的正确方法:

//在线程定义类中
@Override
public void run(){
    try{
        //阻塞代码:sleep、join和wait
    }catch(InterruptedException e){
        //注释掉e.printStackTrace();
        //添加其他业务代码
    }
}

//在线程使用时
thread.interrupt();

 




posted @ 2021-07-30 20:02  姚春辉  阅读(123)  评论(0编辑  收藏  举报