Executor家族一览

笔记来自于Java多线程编程实战指南

这次没有用MD来写笔记,可能排版有点难受


Runnable / Callable 接口是对任务处理逻辑的抽象,也就是说不管怎样的处理逻辑,均有同一的签名方法:run/call,而不关心具体的处理逻辑

Exectuor接口是对任务的执行进行的抽象:也就是说任务提交方(生产者)只需知道execute可使指定的任务被执行,不关心具体的执行细节(比如是由工作者进程还是怎样的线程池来执行)

接口仅定义了 void execute(Runnable command)

抽象的好处:
1.提交与执行解耦
2.信息隐藏/关注点分离,就是OOP中的封装
3.屏蔽同步执行和异步执行的差异,提交到ThreadPoolExecutor就是异步执行,自行implement接口直接在execute中调用run就是同步执行

Executor缺点:
1.只能为客户端代码执行任务,无法将任务结果返回
2.实现类需要维护一些工作者线程,当不再需要该Executor实例时,需要主动释放资源

因此作为改进新增ExecutorService接口(其默认实现是ThreadPoolExecutor)
1.提供submit接受Callable和Runnable并返回Future实例
2.提供shutdown和shutdownNow来关闭相应服务(如关闭维护的工作者线程), PS.shutdownNow内部调用Interrupt方法来实现

util类Executors大概功能一览
1.提供一些好用的ExecutorService线程池实例,如newCachedThreadPool/newFixedThreadPool/newSingleThreadPool(后者无法转换hreadPoolExecutor实例)
2.提供Runnable转换Callable实例
3.返回默认线程工厂defaultThreadFactory

线程池调优选择
Executors给出的各种线程池实例实际上是调用
new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAlive,timeUnit,blockingQueue,threadFactory,rejectExecutionHandler)(够长吧
其中keepAlive和timeUnit用于指定空闲时间,仅负责处理超过核心大小的部分
线程池任务存入工作队列是调用非阻塞的offer(e)
handler用于超过线程池阈值时的处理,该RejectExecutionHandler接口仅定义单一的方法:rejectExecution(runnable,threadPoolExecutor),也有帮你搞定的util实现类:
AbortPolicy(异常)/DiscardPolicy(丢弃当前)/DiscardOldestPolicy(丢弃queue中最老的任务,重新尝试)/CallerRunsPolicy(客户端Caller线程执行)

newCachedThreadPool适用于大量耗时短且提交频繁的任务(耗时过长会导致线程过多而上下文切换频繁),队列使用SynchronousQueue
newFixedThreadPool核心线程池大小=最大线程池大小keepAlive=0(空闲不清理),队列使用LinkedBlockingQueue
newSingleThreadPool适用于单/多生产者但消费者模式,也可用于执行非线程安全对象(不愿意为此加锁)

PS.线程池实例可接受ThreadFactory参数,可用于标准化线程的生产流程(统一配置),比如书中P316的XThreadFactory为线程所做的大概有
1.关联UncaughtExceptionHandler
2.确保用户线程/线程名称含义/优先级..
3.更友好的toString

CompletionService用于一次性批量提交异步任务,暂略

题外话:关于Future和FutureTask

Future接口实例可认为是任务处理结果句柄,大概有那么多定义的方法
1.get()会阻塞以获得执行结果(需要处理InterruptedException和ExecutionException),因此有一个原则是尽量早的submit,尽量晚的get
2.cancel(mayInterrupted)
3.isDone()/isCancelled()/get(time,unit)
也就是说Future使得获知任务的不只是结果(Callable返回的V),还有当前任务的执行情况,不过要处理的异常蛮多的...(还没写全
注意判断完成的情况比较复杂

Runnable优点:可交给简单的工作者线程/Executor的execute/线程池的submit(关于submit需要后面补充)
Runnable缺点:无法得知结果

Callable优点:能得知结果
Callable缺点:只能递交给线程池执行

注意点:Runnable作为executorService的参数时也是可以返回Future的,但源码实现中(具体看AbstractExecutorService的实现)是调用newTaskFor(你可认为就是用了callable转换方法)直接给null作为结果(返回的具体是RunnableFuture,下面会说它是啥)...

FutureTask是Future接口的唯一实现类,它直接实现自RunnableFuture接口
(而RunnableFuture继承自Runnable和Future,可作为两者使用)
其目的就是要结合Runnable和Callable的优点,具体地,就是你实现了Callable示例,直接用它来构造出FutureTask便能转换成Runnable实例,由多态确保能递交给简单的工作者线程/Executor的execute/线程池的submit(具体看源码吧,说起来真绕)
除此以外,FutureTask还能拥有Future的特点,既获得结果(Callable的结果通过get得到而不会因为"转换"成Runnable而丢失),并且能拥有此前提到的关于Future获得任务执行状况和取消的方法
其实类的名字已经说清楚了,既是Future,也是Task

上一篇:是否可以控制每个线程在Java中执行的时间量?


下一篇:Runnable接口和Callable接口的区别