Java InterruptedException 异常

637 阅读5分钟

什么时候抛出

这里引用一段 JDK 源码注释,如果不理解也不要紧,接着往下看

/**
 * 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 {

现在来看两段示例代码,不必理解 interrupt()isInterrupted() 的具体涵义,只需注意两段代码的不同即可

public class InterruptedExceptionTest1 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.format("[%s] 线程执行中...%n",
                Thread.currentThread().getName()));
        thread.start();
        System.out.format("调用interrupt方法前线程是否中断 --> %s%n", thread.isInterrupted());
        thread.interrupt();
        System.out.format("调用interrupt方法后线程是否中断 --> %s%n", thread.isInterrupted());
    }
}

Out:
[main] 调用interrupt方法前线程是否中断 --> false
[Thread-0] 线程执行中...
[main] 调用interrupt方法后线程是否中断 --> true
public class InterruptedExceptionTest2 {
    public static void main(String[] args) {
        System.out.println("main 线程开始执行...");
        Thread thread = new Thread(() -> {
            System.out.format("[%s] 线程开始执行...%n", Thread.currentThread().getName());
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.format("[%s] 线程抛出了 InterruptedException ...%n", 
                    Thread.currentThread().getName());
                System.out.format("[%s] 线程抛出 InterruptedException 后线程是否中断 --> %s%n", 
                    Thread.currentThread().getName(), Thread.currentThread().isInterrupted());
            } finally {
                System.out.format("[%s] 线程执行完毕...%n", Thread.currentThread().getName());
            }
        });
        thread.start();
        System.out.format("[%s] 线程调用interrupt方法前线程是否中断 --> %s%n", 
            Thread.currentThread().getName(), Thread.currentThread().isInterrupted());
        thread.interrupt();
        Thread.sleep(5000);
        System.out.println("main 线程执行完毕...");
    }
}

Out:
main 线程开始执行...
[main] 线程调用interrupt方法前线程是否中断 --> false
[Thread-0] 线程开始执行...
[Thread-0] 线程抛出了 InterruptedException ...
[Thread-0] 线程抛出 InterruptedException 后线程是否中断 --> false
[Thread-0] 线程执行完毕...
main 线程执行完毕...

对比两段代码,同样调用了 interrupt 方法,第二段代码多调用了 sleep 方法 ,就抛出了 InterruptedException,这就对应源码注释中所说:在活动之前或活动期间,线程在等待、睡眠或以其他方式占用并被中断时抛出 InterruptedException

线程阻塞

  1. 等待阻塞:运行的线程执行 wait() 方法,该线程会释放占用的所有资源,JVM 会把该线程放入等待池中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()notifyAll() 方法才能被唤醒
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中
  3. 其他阻塞:运行的线程执行 sleep()join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态

线程中断

每个线程都有一个与之相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false;当另一个线程通过调用 Thread.interrupt() 中断一个线程时,会出现以下两种情况之一:

  1. 如果那个线程在执行一个低级可中断阻塞方法,例如 Thread.sleep()Thread.join()Object.wait(),那么它将取消阻塞并抛出 InterruptedException。参考示例代码二,调用 thread.interrupt() 后抛出了 InterruptedException 并导致 thread 线程提前退出,另外注意,当抛出 InterruptedException 后,会将线程的中断状态 isInterrupted 置为 false
  2. 否则, interrupt() 只是设置线程的中断状态。参考示例代码一,执行了 thread.interrupt() 后,thread.isInterrupted() 返回 true

在被中断线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过 Thread.isInterrupted() 来读取,并且可以通过一个名为 Thread.interrupted() 的操作读取并清除(先读后除)

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

中断的协作特性所带来的一个好处是,它为安全地构造可取消活动提供更大的灵活性。我们很少希望一个活动立即停止;如果活动在正在进行更新的时候被取消,那么程序数据结构可能处于不一致状态。中断允许一个可取消活动来清理正在进行的工作,恢复不变量,通知其他活动它要被取消,然后才终止

处理 InterruptedException

  1. 不捕捉 InterruptedException,将它传播给调用者

    public class TaskQueue {
        private static final int MAX_TASKS = 1000;
     
        private BlockingQueue<Task> queue 
            = new LinkedBlockingQueue<Task>(MAX_TASKS);
     
        public void putTask(Task r) throws InterruptedException { 
            queue.put(r);
        }
     
        public Task getTask() throws InterruptedException { 
            return queue.take();
        }
    }
    
  2. 在重新抛出 InterruptedException 之前执行特定于任务的清理工作

    public class PlayerMatcher {
        private PlayerSource players;
    
        public PlayerMatcher(PlayerSource players) { 
            this.players = players; 
        }
    
        public void matchPlayers() throws InterruptedException { 
            try {
                 Player playerOne, playerTwo;
                 while (true) {
                     playerOne = playerTwo = null;
                     // Wait for two players to arrive and start a new game
                     playerOne = players.waitForPlayer(); // could throw IE
                     playerTwo = players.waitForPlayer(); // could throw IE
                     startNewGame(playerOne, playerTwo);
                 }
             }
             catch (InterruptedException e) {  
                 // If we got one player and were interrupted, put that player back
                 if (playerOne != null)
                     players.addFirst(playerOne);
                 // Then propagate the exception
                 throw e;
             }
        }
    }
    
  3. 捕捉 InterruptedException 后恢复中断状态

    public class TaskRunner implements Runnable {
        private BlockingQueue<Task> queue;
     
        public TaskRunner(BlockingQueue<Task> queue) { 
            this.queue = queue; 
        }
     
        public void run() { 
            try {
                 while (true) {
                     Task task = queue.take(10, TimeUnit.SECONDS);
                     task.execute();
                 }
             }
             catch (InterruptedException e) { 
                 // Restore the interrupted status
                 Thread.currentThread().interrupt();
             }
        }
    }
    
  4. 生吞中断 —— 不要这么做

    // Don't do this 
    public class TaskRunner implements Runnable {
        private BlockingQueue<Task> queue;
     
        public TaskRunner(BlockingQueue<Task> queue) { 
            this.queue = queue; 
        }
     
        public void run() { 
            try {
                 while (true) {
                     Task task = queue.take(10, TimeUnit.SECONDS);
                     task.execute();
                 }
             }
             catch (InterruptedException swallowed) { 
                 /* DON'T DO THIS - RESTORE THE INTERRUPTED STATUS INSTEAD */
             }
        }
    }
    
  5. 如果知道线程正要退出的话,则可以生吞中断

    public class PrimeProducer extends Thread {
        private final BlockingQueue<BigInteger> queue;
     
        PrimeProducer(BlockingQueue<BigInteger> queue) {
            this.queue = queue;
        }
     
        public void run() {
            try {
                BigInteger p = BigInteger.ONE;
                while (!Thread.currentThread().isInterrupted())
                    queue.put(p = p.nextProbablePrime());
            } catch (InterruptedException consumed) {
                /* Allow thread to exit */
            }
        }
     
        public void cancel() { interrupt(); }
    }
    
  6. 在返回前恢复中断状态的不可取消任务

    public Task getNextTask(BlockingQueue<Task> queue) {
        boolean interrupted = false;
        try {
            while (true) {
                try {
                    return queue.take();
                } catch (InterruptedException e) {
                    interrupted = true;
                    // fall through and retry
                }
            }
        } finally {
            if (interrupted)
                // 如果遇到了 InterruptedException,手动将线程中断状态置为 true
                Thread.currentThread().interrupt();
        }
    }
    


参考资料:

~~~~~~~~不学无数——InterruptedException异常处理

~~~~~~~~Java 理论与实践 - 处理 InterruptedException