线程池
我们之前使用线程的时候都是使用new Thread来进行线程的创建,但是这样会有一些问题。如:
a. 每次new Thread新建对象性能差
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
可以通过ThreadPoolExecutor来创建一个线程池
ThreadPoolExecutor类参数最多的构造方法如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数意义
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* 核心线程数。
*
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* 线程池允许创建的最大线程数。如果任务队列满了并且线程数小于maximumPoolSize时,则线程池仍旧会创建新的线程来处理任务。
*
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* 非核心线程闲置的超时时间。超过这个时间则回收。如
*
* @param unit the time unit for the {@code keepAliveTime} argument
* keepAliveTime参数的时间单位。
*
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* 任务队列。如果当前线程数大于corePoolSize,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,也就是阻塞队列。
*
* @param threadFactory the factory to use when the executor
* creates a new thread
* 线程工厂。
*
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* 饱和策略。这是当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。此外还有3种策略,它们分别如下
* CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度
* DiscardPolicy:不能执行的任务,并将该任务删除
* DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务
Java通过Executors提供四种线程池,分别为:
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
corePoolSize和maximumPoolSize都为1,意味着SingleThreadExecutor只有一个核心线程,其他的参数都和FixedThreadPool一样
当执行execute时,如果当前没有运行的线程,则创建一个新线程来处理任务。如果当前有运行的线程,则将任务添加到阻塞队LinkedBlockingQueue中。因此,SingleThreadExecutor能确保所有的任务在一个线程中按照顺序逐一执行。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
FixedThreadPool只有核心线程,并且数量是固定的,没有非核心线程。keepAliveTime设置为0L意味着多余的线程会被立即终止。因为不会产生多余的线程
执行execute方法时,如果当前运行的线程未达到核心线程数时就创建核心线程来处理任务,如果达到了核心线程数则将任务添加到LinkedBlockingQueue中。FixedThreadPool就是一个有固定数量核心线程的线程池,并且这些核心线程不会被回收。当线程数超过corePoolSize 时,就将任务存储在任务队列中;当线程池有空闲线程时,则从任务队列中去取任务执行。
-
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
CachedThreadPool的corePoolSize为0,maximumPoolSize设置为Integer.MAX_VALUE,这意味着CachedThreadPool没有核心线程,非核心线程是无界的。keepAliveTime设置为60L,则空闲线程等待新任务的最长时间为 60s。在此用了阻塞队列 SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。
执行execute方法时,首先会执行offer方法来提交任务,并且查询线程池中是否有空闲的线程执行poll方法来移除任务。如果有则配对成功,将任务交给这个空闲的线程处理;如果没有则配对失败,创建新的线程去处理任务。当线程池中的线程空闲时,它会执行poll方法,等待SynchronousQueue中新提交的任务。如果超过 60s 没有新任务提交到SynchronousQueue,则这个空闲线程将终止。因为maximumPoolSize 是无界的,所以如果提交的任务大于线程池中线程处理任务的速度就会不断地创建新线程。另外,每次提交任务都会立即有线程去处理。所以,CachedThreadPool 比较适于大量的需要立即处理并且耗时较少的任务。 -
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
corePoolSize是传进来的固定数值,maximumPoolSize的值是Integer.MAX_VALUE。因为采用的DelayedWorkQueue是无界的
当执行 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate 或者 scheduleWithFixedDelay方法时,会向DelayedWorkQueue 添加一个实现 RunnableScheduledFuture 接口的ScheduledFutureTask(任务的包装类),并会检查运行的线程是否达到 corePoolSize。如果没有则新建线程并启动它,但并不是立即去执行任务,而是去 DelayedWorkQueue 中取ScheduledFutureTask,然后去执行任务。如果运行的线程达到了corePoolSize时,则将任务添加到DelayedWorkQueue中。DelayedWorkQueue会将任务进行排序,先要执行的任务放在队列的前面。其跟此前介绍的线程池不同的是,当执行完任务后,会将ScheduledFutureTask中的time变量改为下次要执行的时间并放回到DelayedWorkQueue中