JUC
线程中断机制

阿里蚂蚁金服面试题

void interrupt();
static boolean interrupted();
boolean isAlive();
boolean isDaemon();
boolean isInterrupted();
  • 如何中断一个运行中的线程?
  • 如何停止一个运行中的线程?

什么是中断机制

  • 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stopThread.suspendThread.resume 都已经被废弃了。

  • 在 Java 中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java 提供了一种用于停止线程的机制——中断。

  • 中断只是一种协作机制,Java 没有给中断增加任何语法,中断的过程完全需要程序员自己实现。

  • 若要中断一个线程,你需要手动调用该线程的 interrupt 方法,该方法也仅仅是将线程对象的中断标识设成 true;接着你需要自己写代码不断地检测当前线程的标识位,如果为 true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。

  • 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为 true 表示中断,为 false 表示未中断;通过调用线程对象的 interrupt 方法将该线程的标识位设为 true;可以在别的线程中调用,也可以在自己的线程中调用。

中断 API 方法

方法名方法作用
public void interrupt()实例方法;interrupt() 仅仅是设置线程的中断状态为 true,不会停止线程。
public static boolean interrupted()静态方法;Thread.interrupted();
判断线程是否被中断,并清除当前中断状态。该方法有两个作用
1.返回当前线程的中断状态
2.将当前线程的中断状态设置为 false,清除线程的中断状态
(该方法连续调用两次的结果可能不一样)
public boolean isInterrupted()实例方法;判断当前线程是否被中断(通过检查中断标志位)。

面试题中断机制考点

如何停止中断运行中的线程?

通过一个 volatile 变量实现

InterruptDemo.java
public class InterruptDemo {
    static volatile boolean isStop = false;
 
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t hello volatile");
            }
        }, "t1").start();
 
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
 
        new Thread(() -> {
            isStop = true;
        }, "t2").start();
    }
}

通过 AtomicBoolean

InterruptDemo.java
public class InterruptDemo {
 
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
 
    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + "\t atomicBoolean被修改为true,程序停止");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t hello atomicBoolean");
            }
        }, "t1").start();
 
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
 
        new Thread(() -> {
            atomicBoolean.set(true);
        }, "t2").start();
    }
}

通过 Thread 类自带的中断 api 实例方法实现

代码示例
InterruptDemo.java
public class InterruptDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "\t isInterrupted() 被修改为true,程序停止");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t hello interrupt api");
            }
        }, "t1");
        t1.start();
 
        try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
 
        new Thread(t1::interrupt, "t2").start();
    }
}
API 源码分析
Thread.java
/**
 * Tests whether this thread has been interrupted.  The <i>interrupted
 * status</i> of the thread is unaffected by this method.
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if this thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see     #interrupted()
 * @revised 6.0
 */
public boolean isInterrupted() {
    return isInterrupted(false);
}
 
/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);
......
  
/**
 * 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();
}
 
private native void interrupt0();
总结

具体来说,当对一个线程调用 interrupt() 时,

  • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以 interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
  • 如果线程处于被阻塞状态(例如处于 sleep, wait, join 等状态),在别的线程中调用当前线程对象的 interrupt() 方法,那么线程将立即退出被阻塞状态,并抛出 InterruptedException 异常。

当前线程的中断标识为 true,是不是线程就立刻停止?

代码示例

InterruptDemo2.java
public class InterruptDemo {
    public static void main(String[] args) {
        // 实例方法 interrupt() 仅仅是将中断标识位设置为 true,不会中断线程
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; ++i) {
                System.out.println(i);
            }
            System.out.println("t1 线程调用 interrupt() 方法后的中断标识02:" + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        System.out.println("t1 线程默认的中断标识:" + t1.isInterrupted());
 
        try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        t1.interrupt(); // true
        System.out.println("t1 线程调用 interrupt() 方法后的中断标识01:" + t1.isInterrupted()); // true
        try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("t1 线程调用 interrupt() 方法后的中断标识03:" + t1.isInterrupted()); // false 中断不活动的线程不会产生任何效果
 
    }
}
InterruptDemo3.java
public class InterruptDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName()+"\t " +
                            "中断标志位:"+Thread.currentThread().isInterrupted()+" 程序停止");
                    break;
                }
 
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); //中断状态被清除,需要重新设置
                    e.printStackTrace();
                }
 
                System.out.println("-----hello InterruptDemo3");
            }
        }, "t1");
        t1.start();
 
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
 
        new Thread(t1::interrupt,"t2").start();
    }
}
 
/**
 * 1 中断标志位,默认false
 * 2 t2 ----> t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
 * 3 中断标志位true,正常情况,程序停止
 * 4 中断标志位true,异常情况,InterruptedException,将会把中断状态将被清除,并且将收到InterruptedException 。中断标志位false导致无限循环
 * 5 在catch块中,需要再次给中断标志位设置为true,2次调用停止程序才OK
 */

总结

中断只是一种协商机制,修改中断标识位仅此而已,不是立刻 stop 打断。

静态方法 Thread.interrupted(),谈谈你的理解

代码示例

InterruptDemo.java
// 作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,
// 第二次再调用时中断状态已经被清除,将返回一个false。
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
        System.out.println("1");
        Thread.currentThread().interrupt();
        System.out.println("2");
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
        System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
    }
}

说明

The interrupted status of the thread is cleared by this method. In other words, if this method were to be called twice in succession, the second call would return false (unless the current thread were interrupted again, after the first call had cleared its interrupted status and before the second call had examined it).

中断标识被清空,如果该方法被连续调用两次,第二次调用将返回 false;除非当前线程在第一次和第二次调用该方法之间再次被 interrupt

静态方法和实例方法对比

Thread.java
public static boolean interrupted() {return currentThread().isInterrupted(true);}
 
public boolean isInterrupted() {return isInterrupted(false);}
 
// 都调用该方法
private native boolean isInterrupted(boolean ClearInterrupted);

方法的注释也清晰的表达了“中断状态将会根据传入的 ClearInterrupted 参数值确定是否重置”。所以,

  • 静态方法 interrupted() 将 会清除中断状态(传入的参数ClearInterrupted 为 true),

  • 实例方法 isInterrupted() 则不会(传入的参数ClearInterrupted 为 false)。

总结

线程中断相关的方法:

  • interrupt() 方法是一个实例方法
    它通知目标线程中断,也就是设置目标线程的中断标志位为 true,中断标志位表示当前线程已经被中断了。

  • isInterrupted() 方法也是一个实例方法
    它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志

  • Thread 类的静态方法 interrupted()
    返回当前线程的中断状态(boolean 类型)且将当前线程的中断状态设为 false,此方法调用之后会清除当前线程的中断标志位的状态(将中断标志置为 false 了),返回当前值并清零置 false