一。使用interrupt通知而不是强制
线程停止的场景:
一般我们都会让线程运行到结束,但有时比如用户取消了操作,服务需要被快速关闭,服务超时或者出错等。就需要停止线程。
协作机制:
线程的启动很容易但是停止很困难,因为JAVA并没有提供任何机制来安全的终止线程。但它提供了中断(interrupt),一种协作机制,由一个线程来终止另一个线程的当前工作。
thread.interrupt() : 设置中断标志为设为True,仅此而已。程序可以自己选择是否判断中断标志并做处理。
这种协作的方法是很必要的,我们很少希望线程能够立即停止,因为立即停止会使共享的数据结构处于不一致的状态。相反协作的方式:他们会首先清除当前的执行操作,然后再停止。
因为任务本身比发出取消的代码更清楚如何执行清除工作。
良好的系统和勉强运行的系统差别:就是在如何完善处理失败,取消,关闭的场景.
- 1.1 普通中断,run方法中没有sleep,wait时停止线程
public class RightWayStopThreadWithoutSleep implements Runnable { @Override public void run() { int num = 0; // 判断是否中断 while(!Thread.currentThread().isInterrupted() && num < Integer.MAX_VALUE/2) { if(num%10000 == 0) { System.out.println(num); } num ++; } System.out.println("打印结束"); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new RightWayStopThreadWithoutSleep()); thread.start(); Thread.sleep(500);
// 中断子线程 thread.interrupt(); } }
- 1.2 阻塞状态即sleep时被中断
sleep方法会判断中断标志,抛出异常,同时清楚中断标志
public class RightWayStopThreadWithSleep { public static void main(String[] args) throws InterruptedException { Runnable runnable = () ->{ int num =0; while(num<300 && !Thread.currentThread().isInterrupted()) { if(num%100 == 0) { System.out.println(num); } num++; } try { // 睡眠会判断中断标示,而且会将中断标志复位,会抛出异常 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }; Thread t = new Thread(runnable); t.start(); Thread.sleep(500); t.interrupt(); } }
- 1.3 循环阻塞状态即sleep时被中断
public class RightWayStopThreadWithSleepEveryLoop { public static void main(String[] args) throws InterruptedException { Runnable runnable = () -> { int num = 0; try { while (num < 30000) { if (num % 100 == 0) { System.out.println(num); } num++; // 抛出异常,会把本次的中断信号自动清除 Thread.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); } }; Thread t = new Thread(runnable); t.start(); Thread.sleep(500); t.interrupt(); } }
二。中断的传递
被停止方:每次循环或者合适的地方检查中断信号,并在可能抛出interruptException的地方做处理
子方法调用方: 有时在子方法中被中断了,不要吞了异常,
2.1 传递中断:方法申明 public void add() throws InterruptException抛出给最上级的方法处理。
2.2 恢复中断:重新设置中断标示
try { while (num < 30000 && !Thread.currentThread().isInterrupted()) { // 抛出异常,会把本次的中断信号自动清除 Thread.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); }
三。响应中断的方法
这些方法阻塞中会响应中断
Object.wait(), Thread.sleep(), Thread.join()
四。错误的中断方法
4.1 stop会立即停止,程序无法做收尾工作,可能造成脏数据,比如批量转账中10比转了8比突然停止
4.2 volatile变量作为中断标识,某些情况是可行的,但是遇到wait,.sleep阻塞情况就不行了
这种是可行的
/** *演示volatile局限性,part1看似可行 * @author Administrator * */ public class WrongWayVolatile implements Runnable{ // 中断标志,线程间是透明的可见性 private volatile boolean cancled = false; public static void main(String[] args) throws InterruptedException { WrongWayVolatile w = new WrongWayVolatile(); Thread t = new Thread(w); t.start(); Thread.sleep(1000); w.cancled = true; } @Override public void run() { int i = 0; while(i<100000 && !cancled) { if(i%100 == 0) { System.out.println(i); try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
下面这种不可行,ArrayBlockingQueue.put会一直阻塞走不出来了,就不能判断cancled标识
/** * 陷入阻塞时,volatile是无法关闭线程的 此例中生产者生产过快,消费者消费慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费 * * */ public class WrongWayVolatileCantStop { public static void main(String[] args) throws InterruptedException { ArrayBlockingQueue queue = new ArrayBlockingQueue(10); Producer p = new Producer(queue); Thread t = new Thread(p); t.start(); Thread.sleep(1000); Consumer c = new Consumer(queue); while(c.needMoreore()) { System.out.println(c.storage.take()+"被消费了"); Thread.sleep(100); } System.out.println("消费者不需要更多数据"); p.cancled = true; } } class Producer implements Runnable { BlockingQueue storage; // 中断标志,线程间是透明的可见性 public volatile boolean cancled = false; public Producer(BlockingQueue storage) { this.storage = storage; } @Override public void run() { int i = 0; try { while (i < 100000 && !cancled) { if (i % 100 == 0) { // 队列满了时会阻塞,直到队列有空余可以放值了。如果此时interrupt()方法会抛异常, //但是不会处理自定义的cancled标识 storage.put(i); System.out.println(i); Thread.sleep(1); } i++; } } catch (InterruptedException e1) { e1.printStackTrace(); }finally { System.out.println("生产结束"); } } } class Consumer{ BlockingQueue storage; public Consumer(BlockingQueue storage) { this.storage = storage; } public boolean needMoreore() { if(Math.random() > 0.95) { return false; } return true; } }
应该使用interrupt()方法,这样ArrayBlockingQueue.put阻塞时进入waiting状态也可以感知到并抛出异常
五。 无法响应中断时如何停止线程
ReetrantLock.lock是不能响应中断,可以使用ReetrantLock.lockinterruptly()方法来响应
socket io是不能响应的, 有些IO是可以响应中断
对特定情况用特定方法,没有固定方式