要使任务和线程能安全、快速、可靠地停止下来,并不是一件容易的事。Java没有提供任何机制来安全地终止线程。但它提供了中断( Interruption),这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
在Java中,最好的停止线程的方式是使用中断interrupt,但是这仅仅是会通知到被终止的线程“你该停止运行了”,被终止的线程自身拥有决定权(决定是否、以及何时停止),这依赖于请求停止方和被停止方都遵守一种约定好的编码规范。
通常情况下的停止线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public class RightWayStopThreadWithoutSleep {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { int num = 0; while (!Thread.currentThread().isInterrupted() && num <= 100000) { if (num % 100 == 0) { System.out.println(num + "是100的倍数"); } num++; } }); thread.start(); Thread.sleep(10); thread.interrupt(); System.out.println("任务结束了"); } }
|

当在main线程里面调用thread.interrupt()给线程中断的信号时,isInterrupted()方法就会返回true,while条件不满足就会退出循环,此时线程就会退出了。
当线程中断遇到遇到等待状态时
当线程在等待状态时收到中断信号时,会抛出InterruptedException异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); Thread.sleep(500); thread.interrupt(); System.out.println("任务结束了"); } }
|

抛出InterruptedException异常后还会清除interrupt标志位(从true变为false),isInterrupted()方法会
返回false,线程仍然会继续运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { int num = 0; while (!Thread.currentThread().isInterrupted() && num <= 10000) { if (num % 100 == 0) { System.out.println(num + "是100的倍数"); } num++; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(2000); thread.interrupt(); System.out.println("任务结束了"); } }
|

最佳实践
处理中断的最好方法是优先选择在方法上抛出异常。用throws InterruptedException 标记你的方法,不采用try 语句块捕获异常,以便于该异常可以传递到顶层,让run()方法可以捕获这一异常,由run()决定是否停止线程,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class RightWayStopThreadInProd implements Runnable {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new RightWayStopThreadInProd()); thread.start(); Thread.sleep(1000); thread.interrupt(); }
@Override public void run() { while (true && !Thread.currentThread().isInterrupted()) { try { throwInMethod(); } catch (InterruptedException e) { System.out.println("停止线程"); e.printStackTrace(); } } }
private void throwInMethod() throws InterruptedException { Thread.sleep(1000); } }
|
由于run方法内无法抛出checked Exception(只能用try catch),顶层方法必须处理该异常,避免了漏掉或者被吞掉的情况,增强了代码的健壮性。
如果不想或无法传递InterruptedException(例如用run方法的时候,就不让该方法throws InterruptedException),那么应该选择在catch 子句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行依然能够检查到刚才发生了中断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class RightWayStopThreadInProd2 implements Runnable {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new RightWayStopThreadInProd2()); thread.start(); Thread.sleep(1000); thread.interrupt(); }
@Override public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { break; } reInterrupt(); } }
private void reInterrupt() { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } } }
|
还有一种方法是使用volatile来停止线程,volatile能够保证可见性,但是这种停止的方法不够全面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException { BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); Producer producer = new Producer(queue); Thread producerThread = new Thread(producer); producerThread.start(); Thread.sleep(1000); Consumer consumer = new Consumer(queue); while (consumer.needMore()) { System.out.println(consumer.queue.take() + "被消费了"); Thread.sleep(100); } System.out.println("消费者不需要更多数据了"); producer.cancel = true; } }
class Producer implements Runnable {
BlockingQueue<Integer> queue; volatile boolean cancel = false;
public Producer(BlockingQueue<Integer> queue) { this.queue = queue; }
@Override public void run() { int num = 0; try { while (num <= 100000 && !cancel) { if (num % 100 == 0) { queue.put(num); } num++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("生产者结束运行"); } } }
class Consumer { BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) { this.queue = queue; }
public boolean needMore() { Random random = new Random(); return random.nextDouble() <= 0.95; } }
|

当生产者生产太快而消费者消费太慢时,线程可能会阻塞在queue.put()方法导致线程无法感应到cancel的变化而无法退出线程。所以正确的停止线程做法还是使用中断,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;
public class WrongWayVolatileFixed {
public static void main(String[] args) throws InterruptedException { WrongWayVolatileFixed body = new WrongWayVolatileFixed(); BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); Producer producer = body.new Producer(queue); Thread producerThread = new Thread(producer); producerThread.start(); Thread.sleep(1000); Consumer consumer = body.new Consumer(queue); while (consumer.needMore()) { System.out.println(consumer.queue.take() + "被消费了"); Thread.sleep(100); } System.out.println("消费者不需要更多数据了"); producerThread.interrupt(); } class Producer implements Runnable {
BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) { this.queue = queue; }
@Override public void run() { int num = 0; try { while (num <= 100000 && !Thread.currentThread().isInterrupted()) { if (num % 100 == 0) { queue.put(num); } num++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("生产者结束运行"); } } }
class Consumer { BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) { this.queue = queue; }
public boolean needMore() { Random random = new Random(); return random.nextDouble() <= 0.95; } } }
|

总结
除了以上的Thead.sleep()方法和queue.put()方法能够响应中断之外,Object.wait()/Thread.join()等等方法都能够响应中断。