Java线程中断

1,398 阅读6分钟

本文主要介绍Java线程中断一些相关的概念以及注意点

Java线程的中断并不是强制的中断,调用线程中断的方法时只是起到一个通知的作用,至于线程是否要继续执行下去取决于线程自身的处理。

除去已经不推荐使用的thread.stop()方法,主要讲一下线程的成员方法thread.interrupt()、thread.isInterrupted()以及静态方法Thread.interrupted()

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>                                                  
 *                                                                           
 * <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()定义,大致的意思是:

线程中断自己是被允许的。除非当前线程正在中断,否则当前线程的checkAccess()会被调用,这可能会抛出SecurityException异常

当线程被Object中定义的 wait()、wait(long)或wait(long, int)以及线程中的jion()、join(long)、join(long, int)、sleep(long)或sleep(long, int)进入阻塞状态,调用interrupt()时线程的中断标记会被清除,同时抛出一个InterruptedException异常。

如果当前线程在可中断通道的I / O操作中被阻塞,则通道将被关闭,线程的中断状态将被设置为true,并且线程将收到ClosedByInterruptException。

如果当前线程在选择器中被阻塞,那么线程的中断状态将被设置为true,并且它将立即从选择操作中返回,可能具有非零值,就像调用选择器的唤醒方法一样。

如果上述情况都没有发生,那么线程的中断状态将被设置为true。

isInterrupted()、interrupted()

/**                                                                        
 * Tests whether the current thread has been interrupted.  The             
 * <i>interrupted status</i> 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).                     
 *                                                                         
 * <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 the current thread has been interrupted;  
 *          <code>false</code> otherwise.                                  
 * @see #isInterrupted()                                                   
 * @revised 6.0                                                            
 */                                                                        
public static boolean interrupted() {                                      
    return currentThread().isInterrupted(true);                            
} 

上面注释主要是这个意思:

这个方法主要用于测试当前线程是否被中断,同时当前线程的中断状态会被清除。换句话说,连续调用两次这个方法,返回的结果肯定是false,当然有例外,就是第一次调用完这个方法且第二次尚未开始调用的时候,线程再次被中断,也就是中断状态再次被设置为true的情况。

当线程不存活时,线程的中断将被忽略,通过这个方法返回false来反映。

注意点是,这是Thread的静态方法

/**                                                                        
 * 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);                                           
}                                                                          

这个方法和上面的静态方法的主要区别是,这个成员方法调用后并不清除中断状态

线程中断

从上面的3个方法的注释可以看出,线程的中断并不是强制中断,除了可以能抛出异常的情况,都需要自己去处理。对于抛出异常情况,如果自己不想处理,最好是将异常往上传递,可以给其他用户一个处理异常的机会。如果对interrupt不处理又不反馈任何信息,就会像下面这样。

典型的synchronized对线程的中断并不处理

public class ThreadInterruptTest {

    public static void main(String[] args) {
        try {
            Object object = new Object();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        synchronized (object) {
                            Thread.sleep(5000);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            Thread.sleep(2000);

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("get lock");
                    }
                }
            });

            thread.start();

            Thread.sleep(1000);

            thread.interrupt();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

5秒之后打印:

get lock

因为synchronized不处理,线程阻塞在获取锁的状态根本中断不了。

对于想中断的线程,我们可以这样操作:

public class ThreadInterruptTest {

    public static void main(String[] args) {
        try {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            Thread.sleep(1000);
                            System.out.println("in while loop");
                        }
                    } catch (InterruptedException e) {
                        System.out.println("InterruptedException");
                    }
                }
            });

            thread.start();

            Thread.sleep(4000);

            thread.interrupt();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打印如下:

in while loop
in while loop
in while loop
InterruptedException

正常中断了线程

再来看一种特殊情况:

public class ThreadInterruptTest {

    public static void main(String[] args) {
        try {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!Thread.currentThread().isInterrupted()) {
                        try {
                            Thread.sleep(1000);
                            System.out.println("in while loop");
                        } catch (InterruptedException e) {
                            System.out.println("InterruptedException");
                        }
                    }
                }
            });

            thread.start();

            Thread.sleep(4000);

            thread.interrupt();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打印是这样的:

in while loop
in while loop
in while loop
InterruptedException
in while loop
in while loop
in while loop
in while loop
...

程序进入了死循环,猜到什么原因了吗?上面关于thread.interrupt()的注释里面明确说明了,当抛出InterruptedException异常时,线程的中断标记会被清除!!!这就是引起死循环的原因。我们可以这样处理,在捕捉到异常后后加上Thread.currentThread().isInterrupted();再次设置中断的标记。或者像最开始的写法,将整个while循环放try里面。

当然也可以通过自己维护flag的形式来中断线程

public class ThreadInterruptTest {

    private static volatile boolean interruptFlag = false;

    public static void main(String[] args) {
        try {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!interruptFlag) {
                        System.out.println("in while loop");
                    }

                    System.out.println("interrupted");
                }
            }) {
                @Override
                public void interrupt() {
                    super.interrupt();
                    interruptFlag = true;
                }
            };

            thread.start();

            Thread.sleep(2000);

            thread.interrupt();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

最后看一下调用interrupted(),清除中断标记的情况

public class ThreadInterruptTest {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.interrupted());
                System.out.println(Thread.interrupted());
            }
        });

        thread.start();
        thread.interrupt();
    }
}

打印如下:

true
false

总结

  1. 线程调用interrupt()之后并一定会中断,例如在等待synchronized锁的状态下
  2. 线程在sleep()、wait()、jion()等阻塞状态下被中断会抛出InterruptedException异常,同时中断标记被清除
  3. 连续调用两次静态方法interrupted()第二次将返回false(特殊情况注意一下)