线程池ThreadPoolExecutor
1. 池化思想
线程池、字符串常量池、数据库连接池等
线程池作用:提高资源的利用率
2. 不用线程池
- 手动创建线程对象
- 执行任务
- 执行完毕,释放线程对象
3. 线程池优点
- 提高线程利用率
- 提高程序响应速度
- 便于统一管理线程对象
- 可以控制最大并发数
4. 线程池重要的参数
线程池的实现类是ThreadPoolExecutor,它是java.util.concurrent下的类
它有四个构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
我们观察可知,线程池有5个必需的参数,和两个可选参数。
4.1 五个必需参数
4.1.1 corePoolSize(int)
核心线程数。默认情况下不会超时回收的线程。
4.1.2 maximumPoolSize(int)
线程池所能容纳的最大线程数。核心线程都活跃后,如果再有任务进入,则进入任务等待队列。如果等待队列也满了,就会在线程池创建新的线程,处理新进来的请求。如果线程数已经达到线程池所能容纳的最大线程数,且等待队列也满了,新进来的任务就会走拒绝策略。
4.1.3 keepAliveTime(long)
线程闲置超时时长。如果超过该时长,非核心线程会被回收。
4.1.4 unit(TimeUnit)
指定keepAliveTime参数的时间单位,例如毫秒、秒、分。
4.1.5 workQueue(BlockingQueue<Runnable>)
任务等待队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。这个任务队列采用阻塞队列实现。
BlockingQueue<Runnable>有以下7种实现类:
ArrayBlockingQueue
是 BlockingQueue 接口的有界队列实现类,底层采用数组来实现。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。
使用ArrayBlockingQueue时,必须指定其容量大小。
SynchronousQueue
这里的同步说的并不是多线程的并发问题,而是当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走。也就是说,读线程和写线程需要同步,一个读线程匹配一个写线程。
LinkedBlockingDeque
一个双向队列,任何一端都可以进行元素的出入。底层基于单向链表实现的阻塞队列,可无界可有界。
LinkedBlockingQueue
一个单向队列,有FIFO特性,通过两个ReentrantLock和两个Condition来实现。底层基于单向链表实现的阻塞队列,可无界可有界。
DelayQueue
支持延时获取元素的无界阻塞队列,内部用 PriorityQueue 实现。
LinkedTransferQueue
由单向链表组成的无界阻塞队列,其内部节点分为数据节点与请求节点。基于CAS无锁算法实现。
PriorityBlockingQueue
带排序的BlockingQueue实现,通过ReentrantLock控制并发,为无界队列。不可以插入null值,且插入队列的对象必须是可比较大小的。
4.2 两个可选参数
4.2.1 threadFactory(ThreadFactory)
线程工厂。用于指定为线程池创建新线程的方式。
4.2.2 handler(RejectedExecutionHandler)
拒绝策略。当活跃线程已达到线程池最大容量,且阻塞队列也满了时,采取的一种策略 。
5. 线程池如何执行?
ThreadPoolExecutor的继承结构
类 ThreadPoolExecutor 继承 抽象类 AbstractExecutorService
抽象类 AbstractExecutorService 实现 接口 ExecutorService
接口 ExecutorService 继承 接口 Executor
接口 Executor 有一个方法
void execute(Runnable command);
经过ThreadPoolExecutor的重写后,它的源码是这样的
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
我们执行线程池用的就是这个方法。
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
for (int i = 0; i < 8; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 在办理业务");
});
}
executor.shutdown();
}
上面创建的线程池,最大线程容量为5个,有3个核心线程,非核心线程会在闲置超过1s后进行回收,等待队列选择的是ArrayBlockingQueue,长度为3。线程工厂以及拒绝策略都选用的默认。
所以这个线程池最多可以接受8个任务的访问,例如上面我们模拟了8次请求,得到了正确的处理
pool-1-thread-1 在办理业务
pool-1-thread-2 在办理业务
pool-1-thread-5 在办理业务
pool-1-thread-4 在办理业务
pool-1-thread-3 在办理业务
pool-1-thread-2 在办理业务
pool-1-thread-1 在办理业务
如果我们把for循环里的8改成9呢?前八次执行都是正常的,最后一次报错了。
java.util.concurrent.RejectedExecutionException
说明线程池已经满载,采用了拒绝策略。
6. 四种线程池
这是Executors为我们封装好的4种常见功能线程池。
6.1 newCachedThreadPool 可缓存线程池
可无限扩大的线程池,适合处理执行时间比较短的任务。
如果线程池长度超过需要处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
缺点:可能创建非常多的线程,造成资源浪费,甚至OOM。
6.2 newFixedThreadPool 固定数目线程池
定长线程池,可以控制线程最大并发数,超出的任务会在队列中等待。
缺点:如果问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
6.3 ScheduledThreadPool 定时线程池
定长线程池,支持定时即周期性任务执行。
缺点:可能创建非常多的线程,造成资源浪费,甚至OOM。
6.4 newSingleThreadExecutor 单线程化线程池
单线程线程池,保证所有任务先进先出执行。
缺点:可能创建非常多的线程,造成资源浪费,甚至OOM。
6.5 线程池规范性
阿里巴巴Java开发手册告诉我们:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 各个方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。
更多推荐


所有评论(0)