Executor线程池的顶层设计
- 1.任务:实现Runnabl接口或者Callable接口的类
- 2.执行策略:Excutor
- 3.计算结果: Future(异步计算的结果)
主要讲讲2
Executor将工作单元(Runnable)和执行策略分离开来。ThreadPoolExecutor是主要的实现类。ThreadPoolExecutor是这样设计的:
public ThreadPoolExecutor(int corePoolSize, // 基本线程数(线程池预热之后会常驻在内存中)
int maximumPoolSize, // 线程池允许的最大线程数
long keepAliveTime, // 当超过基本线程池的线程空闲时间超过该值,线程退出线程池
TimeUnit unit,
BlockingQueue<Runnable> workQueue, //任务队列
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // 饱和策略
- 1.如果当前运行的线程少于基本线程数,则创建新线程来执行任务(注意,执行这一步骤主要获取全局锁)
- 2.如果运行的线程等于或者小于基本线程,则将任务加入任务阻塞队列
- 3.如果无法将任务加入阻塞队列,队列已满,则创建新的线程来处理任务(注意,执行这一步骤主要获取全局锁)
4.如果创建新的线程将使当前运行的线程超出最大线程数,任务将被拒绝,并调用饱和策略的方法
因为步骤3需要获得全局锁,那将会是一个严重的可伸缩瓶颈,所以通过步骤2减少步骤3,而步骤2是不需要获取全局锁的。
可以通过ThreadPoolExecutor创建三种类型的线程池(Executors的静态方法):1.FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
使用了无界队列LinkedBlockingQueue,所以根据上述,其实设置maximumPoolSize和keepAliveTime都是无效的。不会拒绝任务(调用饱和策略的方法)
- 2.SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
同样使用了LinkedBlockingQueue,造成的效果和FixedThreadPool类似
- 3.CachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
SynchronousQueue是没有容量的工作队列,但是CachedThreadPool中的最大线程数设置为无界。这意味着如果主线程提交任务的速度高于maximumPool中线程处理任务的速度,那么CachedThreadPool会不断创建新线程,极端情况下回耗尽CPU和内存资源。
可以通过ExecutorService的submit方法提交任务,返回Future等待回去任务执行结果。
参考资料:
java并发编程的艺术