JUC包中除了一系列的同步类之外,就是Executor执行框架相关的类。对于一个执行框架来说,可以分为两部分
1. 任务的提交
2. 任务的执行。
这是一个生产者消费者模式,提交任务的操作是生产者,执行任务的线程相当于消费者。
Executor接口设计的目的是专注于任务的执行,和任务的提交解耦。任务的提交由任务的创建者处理。Executor接口封装了任务执行的细节,比如如何使用线程,是否定时执行等等。Executor接口很简单,就一个execute方法。
public interface Executor { void execute(Runnable command); }
如果不使用Executor接口,直接用Thread显式地来执行一个Runnable,代码是这样的
new Thread(new RunnableTask()).start()
而使用Executor接口执行Runnable的代码是这样的
Executor executor = xxxExecutor; executor.execute(new RunnableTask());
可以看到Executor接口的一个是屏蔽了任务执行的细节。它完全可以直接使用Executor所在的线程直接同步执行任务
class DirectExecutor implements Executor { public void execute(Runnable r) { r.run(); } }
class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); } }
《Java并发编程实战》一书中将任务执行的细节称为执行策略。执行策略定义了任务执行的What, Where, When, How
1. 在什么(What)线程中执行任务?
2. 任务按照什么(What)顺序执行(FIFO,LIFO,优先级)
3. 有多少个(How Many)任务能并发执行
4. 在队列中有(How Many)任务在等待执行
5. 如果系统由于过载Overload而需要拒绝一个任务,那么应该选择哪一个(Which)任务?如何(How)通知应用程序有任务被拒绝
6. 在执行一个任务之前或之后,应该进行哪些(What)动作?
这几点都是设计一个执行框架需要考虑的事情。比如ThreadPoolExecutor解决了线程池的问题,ExecutorService解决了执行任务生命周期管理的问题,ScheduleExecutorService解决了定时执行任务的问题。
下面这个在Executor JavaDoc里的串行执行任务的Exector,可以看到如何扩展Executor来自定义执行的策略。
1. 封装了一个ArrayDeque队列来存放待执行的Runnable任务
2. 真正用来执行任务的Executor对象
3. 当前要执行的任务active对象
4. SerialExecutor的execute方法先讲传入的Runnable任务再封装一层,加入到tasks队列,保证这个任务执行完后会去调用scheduleNext()执行下一个任务。然后判断active对象是否是null,表示是第一次执行,就显式地执行scheduleNext().
5. scheduleNext()方法从队列中取任务执行
6. execute()和scheduleNext()都是synchronized方法,保证串行执行。
class SerialExecutor implements Executor { final Queue<Runnable> tasks = new ArrayDeque<Runnable>(); final Executor executor; Runnable active; SerialExecutor(Executor executor) { this.executor = executor; } public synchronized void execute(final Runnable r) { tasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (active == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((active = tasks.poll()) != null) { executor.execute(active); } } }}