线程池
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
上述 ThreadPoolExecutor 拥有的最多参数的构造方法:
-
corePoolSize:核心线程数。默认情况下线程池时空的,只有任务提交时才会创建线程。如果当前运行的线程数小于 corePoolSize,则会创建新线程来处理任务;如果等于或者多于 corePoolSize,则不再创建。如果调用线程池的 prestartAllcoreThread 方法,线程池会提前创建并启动所有的核心线程来等待任务。
-
maximumPoolSize: 线程池允许创建的最大线程数。如果任务队列满了并且线程数小于 maximumPoolSize 时,则线程池仍旧会创建新的线程来处理任务。
-
keepAliveTime:非核心线程闲置的超时时间。超过这个时间则回收。如果任务很多,并且每个任务的执行事件很短,则可以调大 keepAliveTime 来提高线程的利用率。另外,如果是指 allowCoreThreadTimeOut 属性为 true 时,keepAliveTime 也会应用到核心线程上。
-
TimeUnit:keepAliveTime 参数的时间单位。可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、秒(SECONDS)、毫秒(MILLISECONDS)等。
-
WorkQueue: 任务队列。如果当前线程数大于 corePoolSize,则将任务添加到此任务队列中。该任务队列时 BlockingQueue 类型的,也就是阻塞队列。
-
ThreadFactory: 线程工厂。可以用线程工厂给每个创建出来的线程设置名字。一般情况下无须设置该参数。
-
RejectedExecutionHandler: 饱和策略。这是当任务队列和线程池都满了时所采取的应对策略,默认是 AbordPolicy,表示无法处理新任务,并抛出 RejectedExecutionException 异常。此外还有3种策略,它们分别如下。
(1)CallerRunsPoliy: 用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
(2)DiscardPoliy: 不能执行的任务,并将该任务删除。
(3)DiscardOldPolicy: 丢失队列最近的任务,并执行当前的任务。
线程的处理流程是这样的:
提交任务 --》 corePoolSize --》WorkQueue–》maximumPoolSize–》RejectedExecutionHandler
先是创建核心线程数执行任务,核心线程数满了之后才将任务放到任务队列,当任务队列满了之后,看是否达到最大线程数,若没有,则创建非核心线程处理任务,若达到则执行饱和策略。
线程池的种类
有四种线程池比较常用,分别是 FixedThreadPool 、 CachedThreadPool 、 SingleThreadPool 和 ScheduledThreadPool 。
(1)FixedThreadPool
FixedThreadPool 是可重用固定线程数的线程池。在 Executors 类种提供了创建 FixedThreadPool 的方法。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都设置为创建 FixedThreadPool 指定的参数 nThreads ,也就意味着 FixedThreadPool 只有核心线程,并且数量是固定的,没有非核心线程。keepAliveTime 设置为0L 意味着多余的线程会被立即终止。因为不会产生多余的线程,所以 keepAliveTime 是无效的参数。另外,任务队列采用了无界阻塞队列 LinkedBlockingQueue(容量默认为 Integer.MAX_VALUE)。
当执行 execute 方法时,如果当前运行的线程未达到 corePoolSize(核心线程数)时就创建核心线程来处理任务,如果达到了核心线程数则将任务添加到 LinkedBlockingQueue 中。FixedThreadPool 就是一个有固定数量核心线程的线程池,并且这些核心线程不会被回收。当线程数超过 corePoolSize 时,就将任务存储在任务队列中;当线程池有空闲线程时,则从任务队列中去取任务执行。
(2)CachedThreadPool
CachedThreadPool 是一个根据需要创建线程的线程池,创建 CachedThreadPool 的代码如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool 的 corePoolSize 为0,maximumPoolSize设置为 Integer.MAX_VALUE,这意味着 CachedThreadPool 没有核心线程,非核心线程是无界的。空闲线程等待的新任务的最长时间为 60s。在此用了阻塞队列 SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。
当执行 execute 方法是时,首先会执行 SynchronousQueue 的 offer 方法来提交任务,并且查询线程池中是否有空闲的线程执行 SynchronousQueue 的 poll 方法来移除任务。如果有则配对成功,将任务交给这个空闲的线程处理;如果没有则配对失败,创建新的线程去处理任务。当线程池中的线程空闲时,它会执行 SynchronousQueue 的 poll 方法,等待 SynchronousQueue 中新提交的任务。如果超过60s没有新任务提交到 SynchronousQueue ,则这个空闲线程将终止。因为 maximunPoolSize 时无界的,所以如果提交的任务大于线程池中线程处理任务的速度就会不断地创建新线程。另外,每次提交任务都会立即有线程去处理。所以, CachedThreadPool 比较适合于大量的需要立即处理并且耗时较少的任务。
(3)SingleThreadPool
SingleThreadPool是使用单个工作线程的线程池。创建 SingleThreadPool 的代码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
相信这些参数你也看懂了,接着说一下它的流程:
当执行 execute 方法时,如果当前运行的线程未达到核心线程数,也就是当前没有运行的线程,则会创建一个新线程来处理任务。如果当前有运行的线程,则将任务添加到阻塞队列 LinkedBlockingQueue 中,因此,SingleThreadPool 能确保所有的任务在一个线程中按照顺序逐一执行。
(4)ScheduledThreadPool
ScheduledThreadPool 是一个能实现定时和周期性任务的线程池,创建 ScheduledThreadPool 的代码如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
这里创建 ScheduledThreadPoolExecutor,ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,它主要用于给定延时之后的运行任务或定期处理任务。ScheduledThreadPoolExecutor 的构造方法如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
参数跟上面也是一样的,这里就不说了:
当执行 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate 或者 scheduleWithFixedDelay 方法时,会向 DelayedWorkQueue 添加一个实现 RunnableScheduledFututre 接口的 ScheduledFutureTask(任务的包装类),并会检查运行的线程时否达到 corePoolSize。如果没有则新建线程并启动它,但并不是立即去执行任务,而是去 DelayedWorkQueue 中取 ScheduledFutureTask ,然后去执行任务。如果运行的线程达到了 corePoolSize 时,则将任务添加到 DelayedWorkQueue 中,DelayedWorkQueue 会将任务进行排序,先要执行的任务放在队列的前面。其跟之前的介绍到线程池不同的是,当执行完任务后,会将 ScheduledFutureTask 中的 time 变量改为下次要执行的时间并放回到 DelayedWorkQueue 中。