《JAVA并发编程实践》读书笔记(三)

来源:互联网 发布:迪优美特网络机顶盒6 编辑:程序博客网 时间:2024/05/16 10:58

第6章 任务执行
大部分并发应用程序都是围绕“任务执行(Task Execution)”来构造,任务通常是一些抽象的且离散的工作单元。
在线程中执行任务,既不能让服务器串行地工作在单线程下,但同时也不能无限制的创建线程,因而产生Executor框架。线程池简化了线程的管理工作,并且java.util.concurrent提供了一种灵活的线程池实现作为Executor框架的一部分。在Java类库中,任务执行的主要抽象不是Thread,而是Executor。Executor基于生产者-消费者模式,提交任务的操作相当于生产者,执行任务的线程相当于消费者。
6.1 Example
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);

Runnable task = new Runnable(){
public void run(){
handleRequest(connection);
}
}

exec.execute(task);

6.2 线程池
Executors的静态工厂可以调用线程池:
newFixedThreadPool:创建固定长度的线程池,每当提交一个任务时就创建一个线程,如果发生位置的Exception而结束,那么会补充一个新的线程;
newCachedThreadPool: 如果线程池的规模超过处理需求时,将回收空闲线程;如果需求增加时,则可以添加心的线程,线程池的规模不存在任何限制;
newSingleThreadExecutor: 是一个单线程的Executor;
newScheduledThreadPool: 可创建一个固定长度的线程池,而且以延迟或者定时的方式执行任务,类似于Timer。

6.3 Executor的生命周期
JVM只有在所有(非守护)线程全部终止以后才会退出,如果无法正确关闭Executor, 那么JVM将无法结束。
为了解决服务的生命周期问题,Executor扩展了ExecutorService接口,添加了用于管理生命周期的方法。
public interface ExecutorService extends Executor{
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
}

ExecutorService的生命周期有3中状态:运行、关闭和已终止。
shutdown是将执行平缓的关闭过程:不在接受新的任务,同时等待已经提交的任务执行完成——包括那些尚未开始执行的任务。shutdownNow方法将执行粗暴的关闭过程:尝试取消所有运行中的任务,并且不再启动队列中上位开始执行的任务。ExecutorService关闭后提交的任务将由”拒绝执行处理器“来处理,它会抛弃任务,或者使得execute方法抛出一个未检查的RejectedExecutionException。可以调用awaitTermination来等待ExecutorService到达终止状态,或者通过调用isTerminated来轮询ExecutorService是否已经终止。

6.4 携带结果的任务Callable与Future
Callable可以返回值,也可能抛出异常(如果无返回值则为Callable)
Executor有4个生命周期阶段(创建、提交、开始和完成)
Future表示一个任务的生命周期,他提供相应的方法判断是否已经完成或取消,以及获取任务的结果和取消任务等。
Example
Future< List< ImageData >> future = executor.submit(task);
try{
List< ImageData > imageData= future.get();
}catch(InterruptedException e){
Thread.currentThread().interrupt();
future.cancel(true);
}catch(ExecutionException e){
throw launderThrowable(e.getCause());
}


Future.get可以制定超出时间 ad = f.get(timeLeft,NANOSECONDS),超出时间将会抛出TimeoutException,这时要调用f.cancle() 来取消任务。
exec.invokeAll(tasks, time, unit); 可以指定task的超时时间。

第7章 取消与关闭
Java没有安全的抢占式方法来停止任务,只有一些协作式的机制,是请求取消的任务和代码都遵循一种协商好的协议。例如使用volatile类型来保存或者取消状态。

但是,如果队列使用了例如BlockingQueue.put的阻塞机制,很可能永远检查不了取消标志,因此可以使用Thread的interrupt方法。阻塞库方法,例如Thread.sleep和Object.wait等,都会检查线程何时中断,并且在发现中断是提前返回。它们响应中断时执行的操作包括:清除中断状态,抛出InterruptedException, 表示阻塞操作由于中断而提前结束。

通常,中断是实现取消的最合理方式。

由于每个线程拥有各自的中断策略,因此除非你知道中断对于该线程的含义,否则就不应该中断这个线程。

7.1 通过Future来取消中断
ExecutorService.submit将返回一个Future来描述任务,Future拥有一个cancel方法,该方法带有一个boolean类型的参数mayInterruptIfRunning来表示取消操作是否成功。(仅表示任务是否能够接收中断,而不是表示任务是否能检测并处理中断。)
当Future.get抛出InterruptedException或者TimeoutException时,如果你知道不再需要结果,那么就可以调用Future.cancel来取消任务。

7.2 处理不可中断的阻塞
并非所有的可阻塞方法或者阻塞机制都能响应中断;如果线程由于执行同步的Socket I/O 或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此以外没有任何作用。

Java.io包中的同步Socket I/O, 虽然InputStream与OutputStream中的read与write不会响应中断,但通过关闭底层的套接字,可以使得由于执行read 活 write等方法而阻塞的线程抛出一个SocketException.

Java.io包的同步IO。 当中断一个正在 InterruptibleChannel上等待的线程时,将抛出ClosedByInterruptException,并关闭链路。

Selector的异步IO。 如果线程在调用 Selector.select 方法(在 java.nio.channels中)阻塞,那么调用 close 或者 wakeup方法,将抛出 ClosedSelectorException并提前返回。

获取某个锁。 Lock类提供了lockInterruptibly方法,允许在等待一个锁的时候仍然能响应中断。

7.3关闭生产者-消费者服务
切记要清空队列的任务,再关闭。

7.3 关闭ExecutorService
exec.shutdown();
exec.awaitTermination(TIMEOUT, UNIT);

7.4 “毒丸”对象
“毒丸”是指一个放在队列上的对象,其含义是:“当得到这个对象以后,立即停止”。在FIFO队列中,在提交”毒丸“对象之前提交的所有工作都会被处理,而生产者在提交了”毒丸”对象后,将不会在提交任何工作。

7.5 shutdownNow
通过shutdownNow强行关闭ExecutorService时,会尝试取消正在执行的任务,并返回所有已经提交但是尚未开始的任务,从而这些任务可以写入日志或者保存起来以便之后进行处理。通过封装ExecutorService并使得execute记录那些任务是在关闭后取消的,TrackingExecutor可以找到那些任务已经开始但是还没有正常完成。在Executor结束后,getCancelledTasks返回被取消的任务清单。
private volatile TrackingExecutor exec;

exec.shudownNow();
if (exec.awaitTermination(TIMEOUT,UNIT))
exec.getCancelledTasks();

TrackingExecutor可能存在误报,一些被认为已经取消的任务实际上已经执行完成了。

7.6处理非正常的线程终止 (RuntimeException)

7.7未捕获的异常处理
Thread API 提供了 UncaughtExceptionHandler接口,能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给UncaughtExceptionHandler.

7.8 JVM关闭
在正常关闭中,JVM首先调用所有已注册的关闭钩子,关闭钩子是指通过Runtime.addShutdownHook注册的尚未开始的线程。JVM不保证关闭构造的调用顺序。当所有的关闭钩子都执行结束以后,如果runFinalizersOnExit为true,那么JVM将运行终结器,然后再停止。如果关闭钩子或终结器没有执行完成,那么正常关闭进程“挂起”并且JVM必须被强行关闭。当被强行关闭时,只是关闭了JVM,而不会运行关闭钩子。

7.9 守护进程
在JVM启动时创建的所有线程中,除了主线程以外,其他的线程都是守护线程(例如垃圾回收器以及其他执行辅助工作的线程)。当创建一个新线程的时候,新线程将继承创建他线程的守护状态。在默认情况下,主线程创建的所有线程都是普通线程。
当一个线程退出时,JVM会检查其他正在运行的线程,如果全都是守护线程,那么JVM会正常退出操作。当JVM停止时,所有仍然存在的守护进程都会被抛弃。

7.10 终结器
垃圾回收器会对拥有finalize方法的对象进行特殊处理,在回收器释放它们后,调用他们的finalize方法,保证一些持久化资源的释放。(避免使用终结器)

0 0
原创粉丝点击