任务和线程的启动很容易。然而,有时候我们希望提前结束任务或线程,或许是因为用户取消了操作,或者应用程序需要被快速关闭。
要使任务和线程能安全/快速/可靠地停止下来,并不是一件容易的事。Java没有提供任何机制来安全地终止线程,但它提供了中断,这是一种协作机制,能够使一个线程终止另一个线程的工作。
1,任务取消
如果外部代码能在某个操作正常完成之前将其置入“完成”状态,那么这个操作就可以称为可取消的。
协作机制能设置某个标志位,任务会定期查看这个标志,如果设置了标志,那么任务将提前结束。
2,线程中断
线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的活着可能的情况下停止当前工作,并转而执行其他的工作。
每个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。在Thread中包涵了中断线程以及查询线程中断状态的方法。interrupt方法能中断目标线程,而isInterrupted方法能返回目标线程的中断状态,静态的interrupted方法将清除当前的中断状态,并返回它之前的值。
阻塞库方法,例如Thread.sleep()、Object.wait()、Therad.join()等,都会去检查线程何时中断,并且在发现中断时提前返回。他们在响应中断时执行的操作包括: 清除中断状态、抛出InterruptedException,表示阻塞操作由于中断而提前结束。
调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。然后由线程在下一个合适的时刻中断自己。
3,通过Future.cancel()来实现取消
public static void timeRun(Runnable r, long timeout, TimeUnit unit) throws InterruptedException { Future task = taskExec.submit(t); try { task.get(timeout, unit); } catch (TimeoutException e) { } catch (ExecutionException e) { throw launderThrowable(e.getCause()); } finally { task.cancel(true); }}
当Future抛出InterruptedException或TimeoutException时,如果知道不再需要结果,那么就可以调用Future.cancel来取消任务 。
,4,处理不可中断的阻塞
并非所有的可阻塞方法或者阻塞机制都能响应中断;如果一个线程由于执行IO或者等待获得内置锁而阻塞,那么请求只能设置线程的中断状态,除此之外没有任何其他作用。对于那些执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,但这要求我们必须知道线程阻塞的原因 。
Java.io包中的同步Socket I/O: 虽然InputStream和OutputStream中的read和write不会响应中断,但通过关闭底层的套接字,可以使得由于执行read或write方法而阻塞的线程抛出一个SocketException。
获取某个锁:如果一个线程由于等待某个内置锁而阻塞,那么将无法响应中断,因为线程认为它肯定会获得锁,所以不会理会中断请求。但是Lock类中提供了lockInterruptibly方法,该方法允许在等待一个锁的同事仍能响应中断。
所以知道了线程阻塞的原因,还是可以通过捕获异常或者调用自定义中断方法来进行取消操作。
5,采用newTaskFor来封装非标准的取消
newTaskFor是Java 6在ThreadPoolExecutor中的新增功能。当把一个Callable提交给ExecutorService时,submit方法会返回一个Future,可以通过这个Future来取消任务。newTaskFor是一个工厂方法,它将创建Future来代表任务,newTaskFor还能返回一个RunnableFuture接口,该接口扩展了Future和Runnable,并由FutureTask实现。
6,处理非正常的线程终止
导致线程提前死亡的最主要原因是RuntimeException。由于这些异常表示出现了某种编程错误或者其他不可修复的错误,因此它们通常不会被捕获。它们不会在调用栈中逐层传递,而是默认地在控制台输出栈追踪消息,并终止线程。