java并发编程实战-第7章-取消与关闭
java中没有一种安全的抢占式方式的,只有协作式
取消标志如果和阻塞方法一起使用,则会失效,如
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!cancelled)
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) { }
}
public void cancel() { cancelled = true; }
如果任务代码可以响应中断,则可以使用中断作为取消机制
7.1.2 中断策略
形式 :线程级 服务级
只有实现了线程中断策略的代码才可以屏蔽中断请求,在常规的任务和库代码中不应该屏蔽中断请求
7.1.6 处理不可中断的取消
public void interrupt() {
try {
socket.close();
}
catch (IOException ignored) { }
finally {
super.interrupt();
}
}
7.1.7 采用newTaskFor来封装非标准的取消
这是java 6 在ThreadPoolExecutor中的新增功能,这是个工厂方法,创建Future来代表任务,返回
RunnableFutrure接口,改接口扩展了Future和Runnable(并由FutureTask实现)
ThreadPoolExecutor中的cancel是调用newTaskFor返回的FunnableFutrure来取消操作的
通过定制Future 改变Future.cancel的行为,通过改写newTaskFor方法
ThreadPoolExecutor在哪里调用到了Future的cancel方法呢?
父类:AbstractExecutorService 在invokeAll 等invokeXXX方法中的最后finally
...
finally {
if (!done)
for (Future<T> f : futures)
f.cancel(true);
}
7.2 停止基于线程的服务
应用程序拥有服务,服务拥有线程,应用程序没有直接拥有线程,所以不能通过应用程序关闭线程,相反,
服务应该提供生命周期的方法。
比如,ExcutorService提供shutdown 和shiutdownNow方法
7.2.1 示例:日志服务
生产者-消费者
在关闭时候需要给一个isShutDown标志
Loger在消费时判断isShutDown需要同步,生产者在loger的queue.put(msg)的时候也需要同步
如果关闭后,队列中还有内容,则loger还得继续记录日志,用reservations 来计数
7.2.2 关闭ExecutorService
所有权链
使用ExecutorService的日志服务,比7.2.1的示例方面简洁多了
7.2.3 ”毒丸“对象
关闭生产者-消费者的一种方式
毒丸功能和7.2.1 中reservations一样,提供是否还有任务的标志
只有在生产者和消费者都已知的情况下才使用毒丸
7.2.4 示例:只执行一次的服务
7.2.5 shutdownNow的局限性
该方法能返回已提交但未开始的任务,但不会返回关闭时已经开始正在执行的任务
可以用继承的方式扩展功能,添加getCancelledTasks()方法
public class TrackingExecutor extends AbstractExecutorService {
private final ExecutorService exec;
private final Set<Runnable> tasksCancelledAtShutdown =
Collections.synchronizedSet(new HashSet<Runnable>());
...
public List<Runnable> getCancelledTasks() {
if (!exec.isTerminated())
throw new IllegalStateException(...);
return new ArrayList<Runnable>(tasksCancelledAtShutdown);
}
public void execute(final Runnable runnable) {
exec.execute(new Runnable() {
public void run() {
try {
runnable.run();
} finally {
if (isShutdown()
&& Thread.currentThread().isInterrupted())
tasksCancelledAtShutdown.add(runnable);
}
}
});
}
// delegate other ExecutorService methods to exec
}
7.3 处理非正常的线程终止
在GUI丢失事件分派线程的时候情况比较严重,应用程序停止处理事件,GUI停止响应
典型的线程池工作者结构,
public void run() {
Throwable thrown = null;
try {
while (!isInterrupted())
runTask(getTaskFromWorkQueue());
} catch (Throwable e) {
thrown = e;
} finally {
threadExited(this, thrown);
}
}
工作原理:当捕捉到未检测异常时,会终止该线程,但在终止之前,会通知框架,然后由框架来决定是否
用新线程代替或者用线程池的其它线程代替,
ThreadPoolExcutor和Swing都是通过该技术来确保糟糕的任务不会影响到后续任务的执行
7.3.1 未捕获异常的处理UncaughtExceptionHandler
该方法与如上的主动获取方法互补,UncaughtExceptionHandler如何处理异常取决于服务质量的需
求,最常见的是把错误信息以及相应的栈追踪信息写入到应用程序日志中
Listing 7.25. UncaughtExceptionHandler that Logs the Exception.
public class UEHLogger implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.SEVERE,
"Thread terminated with exception: " + t.getName(),
e);
}
}
If you want to be notified when a task fails due to an exception so that you can take some
task-specific recovery action,
either wrap the task with a Runnable or Callable that catches the exception or override the
afterExecute hook in THReadPoolExecutor.
令人困惑的:异常提交给UncaughtExceptionHandler ,只有对于execute有效,对submit无效,如果
submit提交的任务抛出了异常,那么这个异常将被Future.get封装在ExecutionException中重新抛出
参考:zhouchaofei2010.iteye.com/blog/2128389 源代码分析为什么ThreadPoolExcutor的Submit方法不
会把运行时的异常交给UncaughtExceptionHandler处理
7.4 JVM 关闭
7.4.1 关闭钩子
正常关闭:调用关闭钩子
Listing 7.26. Registering a Shutdown Hook to Stop the Logging Service.
public void start() {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try { LogService.this.stop(); }
catch (InterruptedException ignored) {}
}
});
}
非正常:
7.4.2 守护线程
在jvm启动过程中创建的线程。除了主线程,其他的都是守护线程
应少用守护线程,因为jvm 关闭时,守护线程会被抛弃,既不会执行finally代码块,也不会执行回卷栈
7.4.3 终结器
避免使用终结器,用finally代码块或显示close()代替
小结:
java中没有一种安全的抢占式方式的,只有协作式,要依赖于协议与是否遵守这些协议
Using FutureTask and the Executor framework simplifies building cancellable tasks
FutureTask 既可以在ExcutorService中使用,也可以再Thread 中使用