JAVA线程池之ThreadPoolExecutor
ThreadPoolExecutor的七大参数
- @param corePoolSize the number of threads to keep in the pool, even* if they are idle, unless {@code allowCoreThreadTimeOut} isset 池中一直保持的线程的数量,即使线程空闲。除非设置了 allowCoreThreadTimeOut
- @param maximumPoolSize the maximum number of threads to allow inthe* pool 池中允许的最大的线程数
- @param keepAliveTime when the number of threads is greater than* the core, this is the maximumtime that excess idle threads* will wait for new tasks before terminating. 当线程数大于核心线程数的时候,线程在最大多长时间没有接到新任务就会终止释放,最终线程池维持在 corePoolSize 大小
- @param unit the time unit for the {@code keepAliveTime} argument时间单位
- @param workQueue the queue to use for holding tasks before theyare* executed. This queue will hold only the {@code Runnable}* tasks submitted by the {@code execute} method. 阻塞队列,用来存储等待执行的任务,如果当前对线程的需求超过了corePoolSize大小,就会放在这里等待空闲线程执行。
- @param threadFactory the factory to use when the executor creates a new thread创建线程的工厂,比如指定线程名等
/**
* 七大参数
* corePoolSize:[5] 核心线程数[一直存在除非(allowCoreThreadTimeOut)]; 线程池,创建好以后就准备就绪的线程数量,就等待来接受异步任务去执行。
* 5个 Thread thread = new Thread(); thread.start();
* maximumPoolSize:[200] 最大线程数量; 控制资源
* keepAliveTime: 存活时间。如果当前的线程数量大于 core 数量。
* 释放空闲的线程(maximumPoolSize-corePoolSize)。只要线程空闲大于指定的keepAliveTime;
* unit:时间单位
* BlockingQueue<Runnable> workQueue:阻塞队列。如果任务有很多,就会将目前多的任务放在队列里面。只要有线程空闲,就会去队列里面取出新的任务继续执行。
* threadFactory:线程的创建工厂。
* RejectedExecutionHandler handler:如果队列满了,按照我们指定的拒绝策略拒绝执行任务
*/
运行流程
-
1、线程池创建,准备好 core 数量的核心线程,准备接受任务
-
2、新的任务进来,用 core 准备好的空闲线程执行。
- (1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的core 就会自己去阻塞队列获取任务执行
- (2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
- (3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自动销毁。最终保持到 core 大小
- (4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用reject 指定的拒绝策略进行处理
-
3、所有的线程创建都是由指定的 factory 创建的。
总结
简单来说就是多个线程进来先看核心线程数够不够,如果不够进入等待队列,队列满了就用max线程数的数量,还是不够使用淘汰策略。
/**
*
// new Thread(()-> System.out.println("hello")).start();
* 工作顺序:
* 1)、线程池创建,准备好core数量的核心线程,准备接受任务
* 1.1、core满了,就将再进来的任务放入阻塞队列中。空闲的core就会自己去阻塞队列获取任务执行
* 1.2、阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
* 1.3、max满了就用RejectedExecutionHandler拒绝任务
* 1.4、max都执行完成,有很多空闲.在指定的时间keepAliveTime以后,释放max-core这些线程
*
* new LinkedBlockingDeque<>():默认是Integer的最大值。内存不够
*
* 一个线程池 core 7; max 20 ,queue:50,100并发进来怎么分配的;
* 7个会立即得到执行,50个会进入队列,再开13个进行执行。剩下的30个就使用拒绝策略。
* 如果不想抛弃还要执行。CallerRunsPolicy;
*
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
200,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
面试
一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的?
先有 7 个能直接得到执行,接下来 50个进入队列排队,在多开 13 个继续执行。现在70 个被安排上了。剩下 30 个默认拒绝策略。
扩展
使用Excutors包下的几个常用创建线程池方法
常见的 4 种线程池
- A、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- B、newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- C、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
- D、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序 (FIFO、LIFO、优先级) 执行。
// Executors.newFixedThreadPool() 固定大小,core=max;都不可回收
// Executors.newScheduledThreadPool() 定时任务的线程池
// Executors.newSingleThreadExecutor() 单线程的线程池,后台从队列里面获取任务,挨个执行
但是阿里JAVA开发规范并不推荐这么使用,推荐使用ThreadPoolExecutor这种方式
Executors部分方法的弊端:
newFixedThreadPool和newSingleThreadExecutor主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
同时,阿里巴巴java开发规范中推荐了3种线程池创建方式。
- 方式一,引入commons-lang3包。
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
- 方式二,引入com.google.guava包。
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
- 方式三,spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean,调用execute(Runnable task)方法即可。
<bean
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
// in code
userThreadPool.execute(thread);