相关文章:
queue & Deque 详细解析
线程池参数
1. 线程池参数
构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
任务提交,共有2种方法:
public void execute() //提交任务无返回值
public Future<?> submit() //任务执行完成后有返回值
参数解释:
-
corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。核心线程一旦创建,就不会再销毁
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了(已到达corePoolSize上限后,后续任务会加入等待队列),且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
注意:maximumPoolSize=corePoolSize+非核心线程数;假设非核心数是>0的,一旦提交的任务数>corePoolSize,该任务就会被放入阻塞队列中,而不是立即启动一个非核心线程。那么非核心线程是什么时候起的呢?
-
keepAliveTime
线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;仅对非核心线程而言
-
unit
keepAliveTime的单位; -
workQueue
用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供
了如下阻塞队列:- 1、ArrayBlockingQueue:基于数组结构的
有界阻塞
队列,按FIFO排序任务; - 2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
- 3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
- 4、priorityBlockingQuene:具有优先级的
无界阻塞
队列;
构造函数中参数是 BlockingQueue接口,必须是阻塞的
- 1、ArrayBlockingQueue:基于数组结构的
-
threadFactory
它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory()
来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY
优先级并且是非守护线程,同时也设置了线程的名称。 -
handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:- 1、AbortPolicy:直接抛出异常,默认策略;
- 2、CallerRunsPolicy:用调用者所在的线程来执行任务;
- 3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- 4、DiscardPolicy:直接丢弃任务;
上面的4种策略都是ThreadPoolExecutor的内部类。
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
2. 队列
构造函数中参数是 BlockingQueue接口,必须是阻塞的,但是不限制是否有有界。
队列有有界队列和无界队列之分,因此,此时可选队列是:
- 有界阻塞
- 无界阻塞 不建议使用,会导致内存溢出,此时队列永远不会满,
handler
线程池的饱和策略也无法生效。
2.1 为什么是先添加队列而不是先创建最大线程?
就好比一个企业里面有十个(core)正式工的名额,最多招十个正式工(核心线程),要是任务超过正式人数(task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这十个人,但是任务可以稍积压一下。即先放到队列中去(代价低)。十个正式工慢慢干,迟早会干完的,如果任务还在持续增加,超过正式工的加班忍耐极限了(队列满了),就招外包(非核心线程)帮忙了,还是正式工加外包还不能完成任务,那么新来的任务就会被领导拒绝(线程池拒绝策略)。
核心思想是尽量少新增线程或回收线程,如果不先放入队列,就会频繁的进行回收和新增操作,耗费cpu资源。试想临时招工人再辞退,再招再辞退,成本太大。
除了新增或回收线程会耗费资源外,在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。
2.2 线程池中阻塞队列的作用?
-
首先需要队列,是为了削峰。
如果是有界的队列的话,总会满,而我们的机制是满了之后,才会开启新的额外线程。那么等待线程开启的时段内,任务是不能丢弃的。
一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,会直接丢弃,因此需要阻塞队列通过阻塞保留住当前想要继续入队的任务。
-
其次,阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入
wait
状态,释放cpu资源。阻塞队列自带阻塞和唤醒功能,不需要做额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用CPU资源。