线程中断
1.stop()方法
中断Java线程,首当其冲的当然是“方便的”stop()方法啦!直接调用,线程就会中止运行。 但是这个方法却在API中被标明为废弃,这是为什么? 这是正是因为它的“方便”导致的,想象某一个数据处理线程,数据处理到一半, 然后调用stop()方法,线程自然会被中断,但是他还有一半的数据没有处理! 这就破坏了数据的一致性,所以被废弃了。
public class Test { public static void main(String[] args) { User user = new User(); user.setID(1); user.setName(String.valueOf(1)); Thread readThread = new Thread(new ReadTask(user)); readThread.start(); try { while(true) { Thread writeThread = new Thread(new WriteTask(user)); writeThread.start(); Thread.sleep(150); writeThread.stop(); //a deprecated method } } catch (InterruptedException e) { throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e); } } // --------------- // 内部类 // --------------- private static class User { private long ID; private String name; public User() { } public long getID() { return ID; } public void setID(long iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User [ID=" + ID + ", name=" + name + "]"; } } private static class WriteTask implements Runnable { private User user; public WriteTask(User user) { this.user = user; } @Override public void run() { while(true) { long currentTimeMillis = System.currentTimeMillis(); user.setID(currentTimeMillis); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException("the "+ThreadUtil.currentThreadName()+" has been interrupted"); } user.setName(String.valueOf(currentTimeMillis)); } } } private static class ReadTask implements Runnable { private User user; public ReadTask(User user) { this.user = user; } @Override public void run() { while(true) { if(user.getID()!=Long.parseLong(user.getName())) { //一旦出现数据不一致的情况就输出 System.out.println(user); } Thread.yield(); } } } } /***************************** 控制台打印如下 ... User [ID=1489157102274, name=1489157102118] User [ID=1489157102274, name=1489157102118] User [ID=1489157102274, name=1489157102118] User [ID=1489157102274, name=1489157102118] User [ID=1489157102274, name=1489157102118] ... ******************************/
显然,上面这段代码,会在控制台打印大段信息。 WriteTask先setID,然后阻塞自身,如果此时调用了stop, 那么setName就不会调用,数据处理到一半被中断了,从而破坏数据的一致性。 当然,更可怕的是它的静默,这种错误没有任何异常信息,一旦出现问题很难定位,严重降低调试效率。 所以还是尽量不用stop()方法
2.中断位
利用stop()中断线程,其不会通知任何人说:“我要中断了!”,而是自顾自的中断自身。 这样,我们没法做善后处理。理想情况下,如果线程中断了, 要是能够通知我们:“线程中断了,快做一些善后处理!”,就好了。 这时,我们引入一个中断标志位,通过它来判断线程是否中断并做相应的处理。
public class Test { public static void main(String[] args) { User user = new User(); user.setID(1); user.setName(String.valueOf(1)); Thread readThread = new Thread(new ReadTask(user)); readThread.start(); WriteTask writeTask = new WriteTask(user); try { while(true) { //开启多个线程 Thread writeThread = new Thread(writeTask); writeThread.start(); Thread.sleep(150); writeTask.interrupt(); } } catch (InterruptedException e) { throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e); } } //--------------- // 内部类 //--------------- private static class User { private long ID; private String name; public User() { } public long getID() { return ID; } public void setID(long iD) { ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User [ID=" + ID + ", name=" + name + "]"; } } private static class WriteTask implements Runnable { private boolean interrupt; private User user; public WriteTask(User user) { this.user = user; } @Override public void run() { while(true) { synchronized(user) { if(interrupt) { System.out.println("the "+Thread.currentThread().getName()+" has been interrupted"); break; } long currentTimeMillis = System.currentTimeMillis(); user.setID(currentTimeMillis); try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e); } user.setName(String.valueOf(currentTimeMillis)); } Thread.yield(); } } public void interrupt() { this.interrupt = true; } } private static class ReadTask implements Runnable { private User user; public ReadTask(User user) { this.user = user; } @Override public void run() { while(true) { synchronized(user) { if(user.getID()!=Long.parseLong(user.getName())) { //一旦出现数据不一致的情况就输出 System.out.println(user); } } Thread.yield(); } } } } /************************* 控制台打印如下 ... the Thread-2 has been interrupted the Thread-1 has been interrupted the Thread-3 has been interrupted the Thread-4 has been interrupted ... ***************************/
上面这段代码,ReadTask不会打印任何信息了,因为数据的一致性得到保证。 利用设置中断标志位interrupt,“温和的”告知线程:“要中断了,但怎么处理随你”。
3.interrupt()方法
interrupt()方法就是利用上述中断标志位的方式实现线程中断的。 他会调用一个native方法,设置线程的中断标志位。 另外,判断线程是否中断还有两个方法,静态方法interrupted(),实例方法isInterrupted()。 看下源码,JDK1.8的
/** * 一个静态方法,清除当前中断标志 */ public static boolean interrupted() { return currentThread().isInterrupted(true); } /** * 实例方法,不会清除当前中断标志 */ public boolean isInterrupted() { return isInterrupted(false); } /** * 一个本地方法,返回线程中断情况 * 而且当ClearInterrupted为true时, * 调用isInterrupted会清除当前线程的中断标志 */ private native boolean isInterrupted(boolean ClearInterrupted);
我们试试isInterrupted()是如何工作的
public class Test { public static void main(String[] args) { Thread t = new Thread(new Task()); t.start(); t.interrupt(); //设置中断标志位 } private static class Task implements Runnable{ @Override public void run() { System.out.println(Thread.isInterrupted()); //返回当前中断情况,true; System.out.println(Thread.isInterrupted()); //由于isInterrupted()方法清除了中断标志位,所以返回false } } } /********************* 控制台打印如下 true false *********************/
4.中断标志位被清除的情况
当一个线程处于阻塞状态时(sleep、wait、await、join),线程本身就不在运行,如果这时调用了interrupt()方法,就会抛出异常。 而抛出异常时,线程的中断标志位会被清除。
public class Test { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Task()); t.start(); Thread.sleep(2000); t.interrupt(); //设置中断标志位 } private static class Task implements Runnable{ @Override public void run() { try { Thread.sleep(5000); } catch(InterruptedException ex) { System.out.println(Thread.currentThread().interrupted()); //false //throw new RuntimeException(ex); } } } } /******************* 控制台打印如下 false ********************/
上面这段代码,在线程sleep时调用了interrupt,所以抛出了异常,异常的抛出清除了中断标志位 所以interrupted()返回false
总结
1.线程中断方式有stop()和interrupt()两种,其中stop()不安全已弃用
2.interrupt()、interrupted()、isInterrupted()之间的关系
引用
1.《实战Java高并发程序设计》