在Java中,线程中断是一种重要的线程协作机制,从表面上来看,中断就是让目标线程停止执行的意思,实际上却并非如此。
严格上讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出了。至于目标线程接到通知后如何处理,则完全由目标线程自行决定。这点很重要,如果中断后,线程立即无条件退出,这种方式太暴力了,可能会引起一些数据不一致的问题,线程的stop()方法就是因为这个原因被弃用。
与线程中断有关的方法有三个:
1.interrupt():
先看源码:
1 public void interrupt() { 2 if (this != Thread.currentThread()) 3 checkAccess(); 4 5 synchronized (blockerLock) { 6 Interruptible b = blocker; 7 if (b != null) { 8 interrupt0(); // Just to set the interrupt flag 9 b.interrupt(this); 10 return; 11 } 12 } 13 interrupt0(); 14 }
从注释就可以明显看出,interrupt()方法的作用就是“Just to set the interrupt flag”,即仅仅设置中断标识位。
2.isInterrupted():
先看源码:
public boolean isInterrupted() { return isInterrupted(false); }
这个方法通过检查中断标志位,判断当前线程是否被中断。
3.interrupted():
先看源码:
public static boolean interrupted() { return currentThread().isInterrupted(true); }
这是一个静态方法,也是用来判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态。也就是说,连续调用该方法两次,那么第二次的返回值一定是false。
下面举例说明这三个方法:
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(){ @Override public void run(){ while(true){ Thread.yield(); } } }; t1.start(); Thread.sleep(1000); t1.interrupt(); }
上面这个例子,虽然对t1进行了中断,但是运行后发现,线程t1并没有中断,由此可见,即使线程被设置上了中断状态,但是这个中断不会发生任何作用。
如果希望t1在中断后退出,就必须为它增加相应的中断处理代码,将上述例子修改为:
1 public static void main(String[] args) throws InterruptedException { 2 Thread t1 = new Thread(){ 3 @Override 4 public void run(){ 5 while(true){ 6 if (Thread.currentThread().isInterrupted()) { 7 System.out.println("t1 Interrupted!"); 8 break; 9 } 10 Thread.yield(); 11 } 12 } 13 }; 14 t1.start(); 15 Thread.sleep(1000); 16 t1.interrupt(); 17 }
输出结果:
t1 Interrupted!
表示线程中断成功。上述例子增加的部分第6行 Thread.currentThread().isInterrupted() 判断当前线程是否被中断了,如果是,就退出循环体,结束线程。
思考:
存在这样的情况,当线程被阻塞的时候,比如被Object.wait()、Thread.join()或者Thread.sleep()这三种之一的方法阻塞时,该怎么中断线程呢?
有一种情况就是,线程在阻塞前恰好给自己设置了中断标识位,即调用了interrupt()方法,但是线程都阻塞了,当然也就不能通过 isInterrupt()方法来自己中断了;如果中断表示位都没有,就更不可能自己中断了。那么应该怎么办呢?
方法当然是有的,下面以sleep()方法为例,先来看看sleep()方法:
public static native void sleep(long millis) throws InterruptedException;
Thread.sleep()方法会让目标线程休眠若干时间,它会抛出一个 InterruptedException 中断异常。这个异常不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep()时,如果被中断,这个异常就产生了。
看下面的例子:
1 public static void main(String[] args) throws InterruptedException { 2 //将线程取名为t1 3 Thread t1 = new Thread("t1"){ 4 @Override 5 public void run(){ 6 while(true){ 7 if (Thread.currentThread().isInterrupted()) { 8 System.out.println("t1 Interrupted!"); 9 break; 10 } 11 try { 12 System.out.println(Thread.currentThread().getName());//打印出当前线程的名字 13 System.out.println(Thread.currentThread().isInterrupted());//查看当前线程的中断标识位 14 System.out.println("开始睡觉"); 15 Thread.sleep(2000); 16 System.out.println("睡觉结束"); 17 } catch (InterruptedException e) { 18 System.out.println("Interrupted When Sleep!"); 19 System.out.println(Thread.currentThread().isInterrupted());//查看当前线程的中断标识位 20 //设置中断状态 21 Thread.currentThread().interrupt(); 22 } 23 System.out.println("让出资源"); 24 Thread.yield(); 25 } 26 } 27 }; 28 t1.start(); 29 Thread.sleep(1000); 30 System.out.println(Thread.currentThread().getName());//打印出当前线程的名字 31 t1.interrupt(); 32 System.out.println("000000"); 33 }
输出结果:
t1 false 开始睡觉 main 000000 Interrupted When Sleep! false 让出资源 t1 Interrupted!
从输出结果来看,线程t1 start后,是没有中断标识位的,t1线程sleep 2秒,主线程main sleep了 1 秒,当t1还在sleep的时候,main线程执行了 t1.sleep(),所以t1是在sleep的时候被中断,由此捕获到异常,并设置了中断标识位,最后由break 退出中断线程。
注意:
Thread.sleep()方法由于中断而抛出异常,此时,它会清除中断标记,由上面输出结果可以看出在打印出中断后,中断标记位为false,如果不加上中断标识位,线程就不会中断,因此在异常处理中,再次设置上中断标识位。
参考: 《Java高并发程序设计》 葛一鸣 郭超 著