任务取消
取消
- java中没有安全的将占方式来停止线程,只有一些协作式的机制
- 其中一种协作机制,设置某个”已请求取消标志”,而任务将定期的查看该标志,如果设置了这个标志,任务将提前结束,(对于存在阻塞方法的任务,可能永远不会检查到取消标志,导致永远不会结束)
while(!cancelled) //因为put为可阻塞方法,可能导致一直阻塞,导致无法检查取消标志 blockQueue.put(p);
中断
- 每个线程都有一个boolean类型的中断状态,当中断线程时,这个线程的中断状态将被设为true(仅设置状态)
public class Thread{ public void interrupt(){} //设置中断 public boolean isInterrupted(){} //检查是否中断 public static boolean interrupted(){} //清除当前线程的中断状态 }
- 阻塞库方法,如Thread.sleep和Object.wait等方法都会检查线程何时中断,并且在发现中断时提前返回,它们在响应中断时执行的操作包括:清除中断状态,抛出InterruptedException,表示阻塞操作由于中断而提前结束,JVM不能保证阻塞方法检测到中断的速度,但实际情况中响应速度还是非常快的
- 调用interrupt并不意味着立即停止目标线程正在执行的任务,而只是传递了请求中断的消息
- 每个线程都有一个boolean类型的中断状态,当中断线程时,这个线程的中断状态将被设为true(仅设置状态)
中断策略
- 最合理的中断策略是某种形式的线程级(Thread_Level)取消操作或服务级(Service-Level)取消操作;尽快退出,在必要时进行清理,通知某个所有者该线程已经退出
响应中断
当调用可中断的阻塞函数时(中断后会抛出InterruptedException),有两种使用策略用于处理该异常
- 传递(声明抛出)异常,从而使你的方法也成为可中断的阻塞方法
public Task getNextTask() throws InterruptedException{ //传递异常 return blockingQueue.take(); }
- 恢复中断状态,从而使调用栈中的上层代码能够对其进行处理
- 传递(声明抛出)异常,从而使你的方法也成为可中断的阻塞方法
通过Future来实现取消
- Future作为一种抽象机制来管理任务的生命周期,处理异常,以及实现取消.通常使用现有库中的类比自行编写更好
- boolean cancel(boolean mayInterruptRunning)方法参数为false时只能取消还没有开始的任务,对于已经开始的任务,就任由其运行下去;参数为true时,会中断正在执行的任务
//通过Future取消任务 public void timedRun(Runnable r,long timeout,TimeUnit unit) throws InterruptedException{ //交给Future取消任务 Future<?> task = taskExec.submit(r); //taskExec 为ExecutorService try { task.get(timeout,unit); } catch (ExecutionException e) { //可对异常进行处理,抛出详细异常 } catch (TimeoutException e) { //取消任务 } finally { //如果任务已经结束,cancel方法不会有任何影响;如果任务正在运行,将被中断 task.cancel(true); } }
处理不可中断的阻塞
重写interrupt方法将非标准的取消操作封装在Thread中
//重写interrupt方法将非标准的取消操作封装在Thread中 class ReaderThread extends Thread{ private final Socket socket; @Override public void interrupt() { //重写interrupt方法 try { socket.close(); } catch (IOException e) { } finally { super.interrupt(); } } @Override public void run() { //任务 } }
将非标准的取消操作封装在一个任务(Future)中
//扩展Callable接口 interface CancellableTask<T> extends Callable<T>{ void cancel(); RunnableFuture<T> newTask(); } //将非标准的取消操作封装到(Future)任务中 abstract class SocketUsingTask<T> implements CancellableTask<T>{ private Socket socket; //... @Override public synchronized void cancel() { //重写取消操作 try { if(socket != null) socket.close(); } catch (IOException e) {} } @Override public RunnableFuture<T> newTask() { //将此Callable封装为任务返回 return new FutureTask<T>(this){//封装为FutureTask //重写取消方法 @Override public boolean cancel(boolean mayInterruptIfRunning) { try { SocketUsingTask.this.cancel(); //调用Callable重写的cancel方法 } finally { return super.cancel(mayInterruptIfRunning); } } }; } }
停止基于线程的服务
- 服务应该提供生命周期方法(Lifecycle Method)来关闭它自己以及它所拥有的线程,这样服务关闭时,就可以关闭所有线程了,如ExecutorService中提供了shutdown和shutdownNow方法
关闭ExecutorService
- shutdown正常关闭:等待队列中所有任务执行完成后才关闭
- shutdownNow强制关闭:首先关闭当前正在执行的任务,然后返回所有尚未启动的任务清单(无法获知正在执行而被关闭的任务)
毒丸(Poison Pill)对象
未捕获异常的处理
- Thread中提供了UncaughtExceptionHandler,它能检测出某个线程由于未捕获的异常而终结的情况,当一个线程由于未捕获的异常退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器,如果没有提供,那默认将栈追踪信息输出到System.err
- 可以实现该接口,主动的处理异常并写入日志中
- Thread.setUncaughtExceptionHandler为线程设置异常处理器
- Thread.setDefaultUncaughtExceptionHandler设置默认的异常处理器
- ThreadPoolExecutor在构造函数中传入ThreadFactory,就会为线程池所有线程设置一个异常处理器
- 在Executor中由submit提交的任务,不会交UncaughtExceptionHandler,而是被Future.get封装在ExecutionException中重新抛出
JVM关闭
关闭钩子
- 在JVM正常关闭中,JVM首先会调用所有已注册的关闭钩子(Shutdown Hook),关闭钩子并发执行,JVM并不保证关闭钩子的执行顺序,,当所有关闭钩子执行结束,如果runFinalizersOnExit为true,那么JVM将运行终结器,然后在停止,编写的关闭钩子必须要尽快退出,因为它们会延迟JVM的结束时间
- Runtime.getRuntime().addShutdownHook(Threa thread); 注册关闭钩子
//通过注册一个关闭钩子来停止日志服务 public void start(){ Runtime.getRuntime().addShutdownHook(new Thread(){ public void run(){ //停止日志服务 } }) }
守护线程(Daemon Thread)
当JVM中不存在任何一个正在运行的非守护线程时,则JVM进程即会退出,此时会丢弃掉所有现存的守护线程
当JVM停止时,所有仍然存在的守护线程都将被丢弃,既不执行finally块,也不会执行回卷栈,应该尽可能少的私用守护线程
创建一个守护线程,需要在调用thread.start()方法之前调用thread.setDeamon()方法.
终结器
GC回收器对那些定义了finalize方法的对象会进行特殊处理: 在回收器释放他们后,会调用他们的finalize方法,从而保证一些持久化的资源被释放
尽量避免使用终结器(当需要管理对象且该对象持有的资源是通过本地方法获得时才使用)