Java没有提供任何机制来安全地终止线程(虽然Thread.stop和suspend方法提供了这样的机制,但由于存在缺陷,因此应该避免使用
中断:一种协作机制,能够使一个线程终止另一个线程的当前工作
立即停止会使共享的数据结构处于不一致的状态,需要停止时,发出中断请求,被要求中断的线程处理完他当前的任务后会自己判断是否停下来
一、任务取消
若外部代码能在某个操作正常完成之前将其置入“完成”状态,则还操作是可取消的。(用户请求取消、有时间限制的操作<并发查找结果,一个线程找到后可取消其他线程>、应用程序事件、错误、关闭)
取消策略:详细地定义取消操作的“How”、“When”以及“What”,即其他代码如何(How)请求取消该任务,任务在何时(When)检查是否已经请求了取消,以及在响应取消请求时应该执行哪些(What)操作
举例:设置volatile变量为取消标志,每次执行前检查
1 private volatile boolean canceled; 2 3 @Override 4 public void run() { 5 BigInteger p = BigInteger.ONE; 6 while (!canceled){ 7 p = p.nextProbablePrime(); 8 synchronized (this) { //同步添加素数 9 primes.add(p); 10 } 11 } 12 }
注意:这是一个有问题的取消方式,若线程阻塞在add操作后,那么即使设置了取消状态,它也不会运行到检验阻塞状态的代码,因此会永远阻塞
1、中断
线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能的情况下停止当前工作,并转而执行其他的工作。(在取消之外的其他操作使用中断都是不合适的)
调用interrupt并不意味者立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。会在下一个取消点中断自己,如wait, sleep,join等
1 public class Thread { 2 public void interrupt() { ... }//中断目标线程,恢复中断状态 3 public boolean isInterrupted() { ... }//返回目标线程的中断状态 4 public static boolean interrupted() { ... }//清除当前线程的中断状态,并返回它之前的值(用于已经设置了中断状态,但还尚未相应中断) 5 ... 6 }
阻塞库方法,例如Thread.sleep和Object.wait等,都会检查线程何时中断,并且在发现时提前返回。它们在响应中断时执行的操作包括 : 清除中断状态,抛出InterruptedException,表示阻塞操作由于中断而提前结束。
- 显示的检测中断!Thread.currentThread().isInterrupted()后推出
- 阻塞方法中抓到InterruptedException后退出
2、中断策略——规定线程如何解释某个中断请求——当发现中断请求时,应该做哪些工作
由于每个线程拥有各自的中断策略,因此除非你知道中断对该线程的含义,否则就不应该中断这个线程。
3、响应中断
- 传递异常(throws InterruptedException)
- 恢复中断状态,从而事调用栈的上层代码能够对其进行处理。(Thread.currentThread().interrupt();)
4、通过Future实现取消
boolean cancel(boolean mayInterruptIfRunning);
- 如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败,返回false
- 调用cancel时,如果调用成功,而此任务尚未启动,则此任务将永不运行
- 如果任务已经执行,mayInterruptIfRunning参数决定了是否向执行任务的线程发出interrupt操作
5、处理不可中断的阻塞——对于某些阻塞操作,只是设置了中断状态
- Java.io包中的同步Socket I/O。虽然InputStream和OutputStream中的read和write等方法都不会响应中断,但通过关闭底层的套接字,可以使得由于执行read或write等方法而被阻塞的线程抛出一个SocketException。
- Java.io包中的同步I/O。当中断一个正在InterruptibleChannel上等待的线程时,将抛出ClosedByInterruptedException)并关闭链路(这还会使得其他在这条链路上阻塞的线程同样抛出ClosedByInterruptException)。当关闭一个InterruptibleChannel时,将导致所有在链路操作上阻塞的线程抛出AsynchronousCloseException。大多数标准的Channel都实现了InterruptibleChannel。
- Selector的异步I/O。如果一个线程在调用Selector.select方法(在java.nio.channels中)时阻塞了,那么调用close或wakeup方法会使线程抛出ClosedSelectorException并提前返回。
- 获取某个锁。如果一个线程由于等待某个内置锁而被阻塞,那么将无法响应中断,因为线程认为它肯定获得锁,所以将不会理会中断请求。但是,在Lock类中提供了lockInterruptibly方法,该方法允许在等待一个锁的同时仍能响应中断。
1 //改写interrupt方法发出中断请求 2 @Override 3 public void interrupt() { 4 try { 5 socket.close(); //中断前关闭socket 6 } catch (IOException e) { 7 8 } finally{ 9 super.interrupt(); 10 } 11 }
6、采用newTaskFor来封装非标准的取消