线程池

线程池的作用

面向对象程序开发中,对象的创建需要耗费一定的资源。JAVA中也是类似的,JVM试图跟踪每一个对象,以便在对象销毁时进行垃圾回收。线程池技术重点关注如何缩短线程创建与销毁时间。

ThreadPoolExecutor

1
2
3
4
5
6
7
public (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • corePoolSize 线程池中核心线程数
  • maximumPoolSize 线程池维护的最大线程数
  • keepAliveTime 线程池容许线程空闲的时间
  • unit keepAliveTime参数的单位
  • workQueue 任务处理队列
    • ArrayBlockingQueue 基于数组的有界阻塞队列
    • LinkedBlockingQueue 基于链表的阻塞队列
    • SynchronizesQueue 每个插入操作要等另一个线程调用移除操作,否则插入操作一直处于阻塞状态
    • PriorityBlockingQueue 具有优先级的无界阻塞队列
  • threadFactory 创建线程时的工厂。线程工厂有两种选择,DefaultThreadFactoryPrivilegedThreadFactoryDefaultThreadFactory创建一个默认优先级的线程,PrivilegedThreadFactory创建权限控制的线程。
  • handler 超出线程与队列容量时的处理策略,有以下四种:
    • ThreadPoolExecutor.AbortPolicyRejectedExecutionException异常。ThreadPoolExecutorScheduledThreadPoolExecutor线程池的默认处理策略。
    • ThreadPoolExecutor.CallerRunsPolicy 重复尝试添加当前任务。
    • ThreadPoolExecutor.DiscardOldestPolicy 抛弃旧任务,执行当前任务。
    • ThreadPoolExecutor.DiscardPolicy 直接抛弃旧任务。

线程池中添加任务

一个任务通过execute方法添加到线程池中,这个任务必须是一个Runable对象,当任务被执行时,调用的是run方法。

当一个任务被添加到线程池中时,通常经历以下几个步骤:

  1. 线程池中的线程小于corePoolSize,创建新的线程来处理新任务。
  2. 线程池中的线程大于等于corePoolSize,但是缓冲队列workQueue未满,那么将任务加入缓冲队列。
  3. 线程池中的线程大于等于corePoolSize,缓冲队列workQueue已满,并且线程数量未超过最大容量maximumPoolSize,创建新的线程来处理新任务。
  4. 线程池中的线程大于等于corePoolSize,缓冲队列workQueue已满,线程数量超过了最大容量maximumPoolSize,采用handler的策略来处理。

常用线程池

Executor类中提供了几种常用的静态工厂:

newSingleThreadExecutor

1
public static ExecutorService newSingleThreadExecutor(){}

创建只有一个线程工作的线程池,相当于单线程串行执行任务。如果这个唯一的线程由于异常而结束,会有一个新的线程来替代它。此线程池保证任务按照提交的次序顺序执行。

newCachedThreadPool

1
public static ExecutorService newCachedThreadPool() {}

创建一个缓存线程池,若线程池的大小大于任务执行所需的线程数,那么空闲时间大于60s的线程将被回收。若有新任务提交,线程池会动态创建新的线程来进行处理。

newFixedThreadPool

1
2
3
4
5
public static ExecutorService newFixedThreadPool(){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

创建一个指定大小的线程池,每次提交任务,都会创建一个新的线程,直到线程数量达到线程池的最大值,以后新提交的任务都会存放在等待队列中。若线程池中的线程因异常而退出,线程池会补充一个线程。

newSingleThreadScheduledExecutor

1
2
3
4
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}

创建一个单线程的线程池,该线程池还支持定时与周期性执行任务。

newScheduledThreadPool

1
2
3
4
5
6
7
8
9
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}

类似newCachedThreadPool,创建一个缓冲线程池,该线程池同时还支持定时与周期性执行任务。

有界队列

对于有界队列,有任务提交时,采用以下处理方式:

  • 若线程池中线程数小于corePoolSize,直接新建一个线程,立即处理。
  • 若线程池中线程数大于等于corePoolSize,且任务处理队列workQueue未满,则将任务存入队列中。
  • 若线程池中线程数大于等于corePoolSize,任务处理队列workQueue已满,且未超过最大线程数maximumPoolSize,则尝试新建一个线程进行紧急处理。
  • 若以上三步都无法处理,则执行拒绝策略。

无界队列

相比于有界队列,无界队列不存在任务入队出错的情况,具体处理方式如下:

  • 若线程池中线程数小于corePoolSize,直接新建一个线程,立即处理。
  • 若线程池中线程数大于等于corePoolSize,则将任务存入任务处理队列workQueue中。若任务创建与处理速度差异很大,则无界队列会保持快速增长,直到耗尽系统内存。

PS:说LinkedBlockingQueue是无界队列是不恰当的,其长度是Integer.MAX_VALUE

线程池初始化

默认情况下,创建线程池后,线程池中是没有线程的,需要提交任务才会创建线程。
当然如果需要创建线程池后就立即创建线程,可以使用下面两个方法:

  1. prestartCoreThread() 初始化一个核心线程
  2. prestartAllCoreThreads() 初始化所有核心线程

execute与submit区别

executesubmit都是向线程池提交任务,但是submit会返回一个future,通过future可以获取任务运行的结果。