Java多线程-07丨线程中断机制

Posted by jiefang on November 1, 2019

线程中断机制

Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。

主要涉及内容:

  • interrupt flag:中断标志位;
  • InterruptedException:中断异常;
  • interrupt():设置线程中断标志;
  • interrupted():返回线程中断标志状态,并重置线程中断标志;
  • isInterrupted():返回线程中断标志状态;

interrupt flag

java中,每一个线程都有一个中断标志位,表征了当前线程是否处于被中断状态,我们可以把这个标识位理解成一个boolean类型的变量,当我们中断一个线程时,将该标识位设为true,当我们清除中断状态时,将其设置为false。

Thread线程类里面,并没有类似中断标志位的属性,但是提供了获取中断标志位的方法:

1
2
//该方法除了能够返回当前线程的中断状态,还能根据ClearInterrupted参数来决定要不要重置中断标志位
private native boolean isInterrupted(boolean ClearInterrupted);

isInterrupted()

isInterrupted()调用了isInterrupted(false), ClearInterrupted参数为false, 说明它仅仅返回线程实例的中断状态,但是不会对现有的中断状态做任何改变。

1
2
3
public boolean isInterrupted() {
    return isInterrupted(false);
}

interrupted()

interrupted是一个静态方法,所以它可以由Thread类直接调用,自然就是作用于当前正在执行的线程,所以函数内部使用了currentThread()方法,与isInterrupted()方法不同的是,它的ClearInterrupted参数为true,在返回线程中断状态的同时,重置了中断标识位。

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

InterruptedException

线程中断异常源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Thrown when a thread is waiting, sleeping, or otherwise occupied,
 * and the thread is interrupted, either before or during the activity.
 * Occasionally a method may wish to test whether the current
 * thread has been interrupted, and if so, to immediately throw
 * this exception.  The following code can be used to achieve
 * this effect:
 * <pre>
 *  if (Thread.interrupted())  // Clears interrupted status!
 *      throw new InterruptedException();
 * </pre>
 *
 * @author  Frank Yellin
 * @see     java.lang.Object#wait()
 * @see     java.lang.Object#wait(long)
 * @see     java.lang.Object#wait(long, int)
 * @see     java.lang.Thread#sleep(long)
 * @see     java.lang.Thread#interrupt()
 * @see     java.lang.Thread#interrupted()
 * @since   JDK1.0
 */
public class InterruptedException extends Exception {}

Thread.sleep()方法抛出中断异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

如下情况会抛出线程中断异常:

  • wait()
  • wait(long)
  • wait(long timeout, int nanos)
  • sleep(long)
  • sleep(long millis, int nanos)
  • join()
  • join(long millis)
  • join(long millis, int nanos)

虽然这些方法会抛出InterruptedException,但是并不会终止当前线程的执行,当前线程可以选择忽略这个异常。

也就是说,无论是设置interrupt status 还是抛出InterruptedException,它们都是给当前线程的建议,当前线程可以选择采纳或者不采纳,它们并不会影响当前线程的执行。至于在收到这些中断的建议后,当前线程要怎么处理,也完全取决于当前线程。

interrupt()

Thread.interrupt()源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * 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>
 *
 * <p> Interrupting a thread that is not alive need not have any effect.
 *
 * @throws  SecurityException
 *          if the current thread cannot modify this thread
 *
 * @revised 6.0
 * @spec JSR-51
 */
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方法之后),线程的中断标志会被清除,并且收到一个InterruptedException:

  • Object的方法
    • wait()
    • wait(long)
    • wait(long, int)
  • Thread的方法
    • join()
    • join(long)
    • join(long, int)
    • sleep(long)
    • sleep(long, int)
  • 如果线程没有因为上面的函数调用而进入阻塞状态的话,那么中断这个线程仅仅会设置它的中断标志位(而不会抛出InterruptedException)
  • 中断一个已经终止的线程不会有任何影响。

Thread.interrupt()所谓“中断一个线程”,其实并不是让一个线程停止运行,仅仅是将线程的中断标志设为true, 或者在某些特定情况下抛出一个InterruptedException,它并不会直接将一个线程停掉,在被中断的线程的角度看来,仅仅是自己的中断标志位被设为true了,或者自己所执行的代码中抛出了一个InterruptedException异常,仅此而已

终止线程

如何安全的终止一个线程:

  • 处于运行中的线程,通过不断地检查中断标志位来实现中断。
  • 中断一个调用wait,sleep,join方法而进入阻塞状态的线程,则这些方法将会抛出InterruptedException异常,使线程跳出阻塞状态,从而终止线程。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    @Override
    public void run() {
      try {
          // 1. isInterrupted() 用于终止一个正在运行的线程。
          while (!isInterrupted()) {
              // 执行任务...
          }
      } catch (InterruptedException ie) {  
          // 2. InterruptedException异常用于终止一个处于阻塞状态的线程
      }
    }
    

总结

中断机制的核心在于中断状态InterruptedException异常

中断状态:

  • 设置一个中断状态: Thread#interrupt()
  • 返回线程原来的中断状态,并清除中断状态: Thread#interrupted()
  • 返回线程当前的中断状态而不清除:Thread#isInterrupted()

中断异常:

  • 中断异常一般是线程被中断后,在一些block类型的方法(如wait,sleep,join)中抛出。

Thread#interrupt()中断线程的效果:

  • 若被中断前,该线程处于非阻塞状态,那么该线程的中断状态被设为true, 除此之外,不会发生任何事。
  • 若被中断前,该线程处于阻塞状态(调用了wait,sleep,join等方法),那么该线程将会立即从阻塞状态中退出,并抛出一个InterruptedException异常,同时,该线程的中断状态被设为false, 除此之外,不会发生任何事。

无论是中断状态的改变还是InterruptedException被抛出,这些都是当前线程可以感知到的”建议”,如果当前线程选择忽略这些建议(例如简单地catch住异常继续执行),那么中断机制对于当前线程就没有任何影响,就好像什么也没有发生一样。