我们最常使用的Executors实现创建线程池使用线程主要是用上述类图中提供的类。在上边的类图中,包含了一个Executor框架,它是一个根据一组执行策略的调用调度执行和控制异步任务的框架,目的是提供一种将任务提交与任务如何运行分离开的机制。它包含了三个executor接口:
参数说明:ThreadPoolExecutor一共有七个参数,这七个参数配合起来,构成了线程池强大的功能。
corePoolSize、maximumPoolSize、workQueue 三者关系:如果运行的线程数小于corePoolSize的时候,直接创建新线程来处理任务。即使线程池中的其他线程是空闲的。如果运行中的线程数大于corePoolSize且小于maximumPoolSize时,那么只有当workQueue满的时候才创建新的线程去处理任务。如果corePoolSize与maximumPoolSize是相同的,那么创建的线程池大小是固定的。这时有新任务提交,当workQueue未满时,就把请求放入workQueue中。等待空线程从workQueue取出任务。如果workQueue此时也满了,那么就使用另外的拒绝策略参数去执行拒绝策略。
初始化方法:由七个参数组合成四个初始化方法
其他方法:
execute(); //提交任务,交给线程池执行
submit();//提交任务,能够返回执行结果 execute+Future
shutdown();//关闭线程池,等待任务都执行完
shutdownNow();//关闭线程池,不等待任务执行完
getTaskCount();//线程池已执行和未执行的任务总数
getCompleteTaskCount();//已完成的任务数量
getPoolSize();//线程池当前的线程数量
getActiveCount();//当前线程池中正在执行任务的线程数量
线程池生命周期:
使用Executors可以创建四种线程池:分别对应上边提到的四种线程池初始化方法
Executors.newCachedThreadPool
newCachedThreadPool是一个根据需要创建新线程的线程池,当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。 对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常。
//创建newCachedThreadPool线程池源码
public static ExecutorService newCachedThreadPool() {
/**
*corePoolSize: 0,核心线程池的数量为0
*maximumPoolSize: Integer.MAX_VALUE,可以认为最大线程数是无限的
*keepAliveTime: 60L
*unit: 秒
*workQueue: SynchronousQueue
**/
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
使用案例:
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
executor.execute(new Runnable() {
@Override
public void run() {
log.info("task:{}",index);
}
});
}
}
值得注意的一点是,newCachedThreadPool的返回值是ExecutorService类型,该类型只包含基础的线程池方法,但却不包含线程监控相关方法,因此在使用返回值为ExecutorService的线程池类型创建新线程时要考虑到具体情况。
Executors.newSingleThreadExecutor
newSingleThreadExecutor是单线程线程池,只有一个核心线程,用唯一的一个共用线程执行任务,保证所有任务按指定顺序执行(FIFO、优先级…)
//newSingleThreadExecutor创建线程池源码
public static ExecutorService newSingleThreadExecutor() {
/**
* corePoolSize : 1,核心线程池的数量为1
* maximumPoolSize : 1,只可以创建一个非核心线程
* keepAliveTime : 0L
* unit => 秒
* workQueue => LinkedBlockingQueue
**/
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
当一个任务提交时,首先会创建一个核心线程来执行任务,如果超过核心线程的数量,将会放入队列中,因为LinkedBlockingQueue是长度为Integer.MAX_VALUE的队列,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常,同时因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程。
Executors.newFixedThreadPool
定长线程池,核心线程数和最大线程数由用户传入,可以设置线程的最大并发数,超出在队列等待
//newFixedThreadPool创建线程池源码
public static ExecutorService newFixedThreadPool(int nThreads) {
/**
* corePoolSize : 核心线程的数量为自定义输入nThreads
* maximumPoolSize : 最大线程的数量为自定义输入nThreads
* keepAliveTime : 0L
* unit : 秒
* workQueue : LinkedBlockingQueue
**/
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
newFixedThreadPool和SingleThreadExecutor类似,唯一的区别就是核心线程数不同,并且由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常。
Executors.newScheduledThreadPool
定长线程池,核心线程数由用户传入,支持定时和周期任务执行
//newScheduledThreadPool创建线程池源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
/**
* corePoolSize : 核心线程的数量为自定义输入corePoolSize
* maximumPoolSize : 最大线程的数量为Integer.MAX_VALUE
* keepAliveTime : 0L
* unit : 纳秒
* workQueue : DelayedWorkQueue
**/
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
当一个任务提交时,corePoolSize为自定义输入,首先创建核心线程,核心线程满了之后,因此最终会创建非核心线程来执行任务。非核心线程使用后将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常。因为使用的DelayedWorkQueue可以实现定时和周期任务。 ScheduledExecutorService提供了三种方法可以使用:
schedule:延迟后执行任务 scheduleAtFixedRate:以指定的速率执行任务 scheduleWithFixedDelay:以指定的延迟执行任务 使用案例:
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
// executorService.schedule(new Runnable() {
// @Override
// public void run() {
// log.warn("schedule run");
// }
// //延迟3秒后执行
// }, 3, TimeUnit.SECONDS);
// executorService.shutdown();
// executorService.scheduleWithFixedDelay(new Runnable() {
// @Override
// public void run() {
// log.warn("scheduleWithFixedDelay run");
// }
// //延迟一秒后每隔3秒执行
// }, 1, 3, TimeUnit.SECONDS);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
log.warn("schedule run");
}
//延迟一秒后每隔3秒执行
}, 1, 3, TimeUnit.SECONDS);
/**
* 定时器调度,不推荐使用,推荐ScheduledExecutorService调度
*/
// Timer timer = new Timer();
// timer.schedule(new TimerTask() {
// @Override
// public void run() {
// log.warn("timer run");
// }
// //从当前时间每隔5秒执行
// }, new Date(), 5 * 1000);
}
总结
这就是为什么禁止使用Executors去创建线程池,而是推荐自己去创建ThreadPoolExecutor的原因
CPU密集型 :线程池的大小推荐为CPU数量 + 1,CPU数量可以根据Runtime.availableProcessors方法获取 IO密集型 : CPU数量 * CPU利用率 * (1 + 线程等待时间/线程CPU时间) 混合型 : 将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,从而使每个线程池可以根据各自的工作负载来调整 阻塞队列 : 推荐使用有界队列,有界队列有助于避免资源耗尽的情况发生 拒绝策略 : 默认采用的是AbortPolicy拒绝策略,直接在程序中抛出RejectedExecutionException异常【因为是运行时异常,不强制catch】,这种处理方式不够优雅。处理拒绝策略有以下几种比较推荐:
如果使用Executors的静态方法创建ThreadPoolExecutor对象,可以通过使用Semaphore对任务的执行进行限流也可以避免出现OOM异常