一、基本概念
1.1 什么是线程池
线程池是一种基于池化思想管理线程的一种工具
1.2 线程池的利弊
线程数过多会增加许多的开销,如创建线程的开销、调度线程的开销、销毁线程的开销,因此使用线程池就有以下的好处
- 降低资源消耗:利用池化技术重复利用创建的线程,避免了创建和销毁线程
- 提高响应速度:在任务已到达就能有线程去执行,无需创建线程去执行
- 提高线程的可管理性:由于线程很重要,如果无限制的创建线程,它们会相互竞争,不仅会导致系统资源的消耗,而且会降低系统的稳定性,最坏将会导致死机或者是OOM
- 提供更多的功能:除了一些基础功能之外,通过创建不同的线程池可以拥有一些额外的功能,例如可以提供定时执行、定期执行
线程池在拥有一定好处的同时也会有一定的弊端
- 死锁:这是多线程的通病,在某些场景下有可能触发死锁,例如存在一些交互的对象,这些对象可以相互发送查询,当这些查询被放入到阻塞队列后就可能导致A对象所需要的查询是需要等待其所属B对象,而B对象也在等待A对象所属的查询
- 资源不足:如果线程池设置的过大,线程所消耗的资源可能会影响到系统的性能。
1.3 线程池解决了什么问题
线程池核心是解决的对于资源的管理。在并发环境下由于系统无法准确的去评估以及管理其内部的资源,所以会导致系统资源得不到很好的利用,而且无法对其申请的资源做出限制导致系统出现问题,频繁的创建、销毁资源也会额外增加消耗。
而线程池这种池化的思想能够将资源统一起来管理,而且能够进行复用,比之随便乱用资源要好上很多,除了线程池有这样,还有其他的使用这种策略的”池子“
- 连接池:数据库中常用
- 内存池:预先申请内存,减少内存碎片
- 实力池:循环利用对象,减少资源初始化带来的额外消耗
二、线程池核心设计与实现
Java中的线程池是由ThreadPoolExecutor来实现的,先来看下其UML类图
图中可以看出,该类是最终是实现了Executor接口,这个接口只提供了一个方法execute(Runnable command)。Executor接口只是提供了一个思想,就是将任务的提交和执行进行了解耦,用户无需关心任务是如何执行的,只需要提供一个Runnable,然后将其提交给Executor即可。中间两个组件做一些增强,最主要的内容还是在ThreadPoolExecutor中实现的,它需要维护自身的生命周期,而且还需要管理线程和任务,使两者更好的运行。
ThreadPoolExecutor的模型及其运行机制如下:
从上图可知,线程池主要是分为了两个部分,任务和线程,在任务提交以后线程池会判断任务是直接执行还是进入阻塞队列等待执行还是会拒绝该任务。线程被统一维护在线程池中,根据任务来进行分配,在线程完成一个任务之后又会去获取任务继续执行,直到没有任务后,线程将会被回收。在对线程池的运行机制深入了解前,先对ThreadPoolExecutor中的参数和方法有个大体的了解
//该值可以用来判断线程池的运行状态和线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Packing and unpacking ctl
//判断线程池的运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程池中的当前的线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//通过数量以及状态来生成ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
//阻塞队列
private final BlockingQueue<Runnable> workQueue;
/**
* Lock held on access to workers set and related bookkeeping.
* While we could use a concurrent set of some sort, it turns out
* to be generally preferable to use a lock. Among the reasons is
* that this serializes interruptIdleWorkers, which avoids
* unnecessary interrupt storms, especially during shutdown.
* Otherwise exiting threads would concurrently interrupt those
* that have not yet interrupted. It also simplifies some of the
* associated statistics bookkeeping of largestPoolSize etc. We
* also hold mainLock on shutdown and shutdownNow, for the sake of
* ensuring workers set is stable while separately checking
* permission to interrupt and actually interrupting.
*/
//对象锁,中间有些方法有用到
private final ReentrantLock mainLock = new ReentrantLock();
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
//存储工作线程
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
* Tracks largest attained pool size. Accessed only under
* mainLock.
*/
//线程池从创建到现在,池中线程的最大数量
private int largestPoolSize;
/**
* Counter for completed tasks. Updated only on termination of
* worker threads. Accessed only under mainLock.
*/
//线程池已经完成的线程数
private long completedTaskCount;
/*
* All user control parameters are declared as volatiles so that
* ongoing actions are based on freshest values, but without need
* for locking, since no internal invariants depend on them
* changing synchronously with respect to other actions.
*/
/**
* Factory for new threads. All threads are created using this
* factory (via method addWorker). All callers must be prepared
* for addWorker to fail, which may reflect a system or user's
* policy limiting the number of threads. Even though it is not
* treated as an error, failure to create threads may result in
* new tasks being rejected or existing ones remaining stuck in
* the queue.
*
* We go further and preserve pool invariants even in the face of
* errors such as OutOfMemoryError, that might be thrown while
* trying to create threads. Such errors are rather common due to
* the need to allocate a native stack in Thread.start, and users
* will want to perform clean pool shutdown to clean up. There
* will likely be enough memory available for the cleanup code to
* complete without encountering yet another OutOfMemoryError.
*/
//构造参数,用于创建线程
private volatile ThreadFactory threadFactory;
/**
* Handler called when saturated or shutdown in execute.
*/
//拒绝策略
private volatile RejectedExecutionHandler handler;
/**
* Timeout in nanoseconds for idle threads waiting for work.
* Threads use this timeout when there are more than corePoolSize
* present or if allowCoreThreadTimeOut. Otherwise they wait
* forever for new work.
*/
//空闲线程池的存活时间
private volatile long keepAliveTime;
/**
* If false (default), core threads stay alive even when idle.
* If true, core threads use keepAliveTime to time out waiting
* for work.
*/
//是否允许核心线程在空闲状态下自行销毁
private volatile boolean allowCoreThreadTimeOut;
/**
* Core pool size is the minimum number of workers to keep alive
* (and not allow to time out etc) unless allowCoreThreadTimeOut
* is set, in which case the minimum is zero.
*/
//核心线程数量
private volatile int corePoolSize;
/**
* Maximum pool size. Note that the actual maximum is internally
* bounded by CAPACITY.
*/
//线程池中允许存在的最大线程数量
private volatile int maximumPoolSize;
/**
* The default rejected execution handler
*/
//默认的拒绝策略
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
2.1 线程池的生命周期
线程池有两个重要的状态值,一是线程池的运行状态(runState),二是运行中的线程数量(workerCount),这两个内容是放在同一个值下面维护的,即 ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
这个参数是一个AtomicInteger,它是用来控制线程池的运行状态以及线程池中的线程数量的。它包含了两个部分,高3位是用来存储线程池的运行状态,低29位是用来存储线程池中的线程数量。将这两个内容放在一起是因为在线程池中的许多操作中需要同时去获取线程池的状态以及其中的线程数量,如果将这两者分开在需要保证两者一致是需要消耗一定的锁资源。
在ThreadPoolExecutor中也提供了获取线程池状态和线程数量等相关的操作
//判断线程池的运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程池中的当前的线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//通过数量以及状态来生成ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
ThreadPoolExecutor的运行状态有下面五种:
运行状态 | 状态描述 |
---|---|
RUNNING | 能接受新的任务,并且也能处理阻塞队列中的任务 |
SHUTDOWN | 无法接收新的任务,但是能够继续处理阻塞队列中的任务 |
STOP | 无法接收新的任务,也无法处理阻塞队列中的任务,正在处理任务的线程也会停止 |
TIDYING | 所有任务都已经终止,其中有效的线程数量也为0 |
TERMINATED | 在调用terminated()方法后进入该状态 |
如此,其生命周期如下图所示
2.2 线程池的任务调度机制
这一部分内容就是线程池运行机制的核心了,在这里决定的一个任务是如何执行的。
ThreadPoolExecutor的入口是execute()
方法,在这个方法内会通过workerCountOf()
方法去判断工作中的线程,然后会通过isRunning()
方法去判断线程池是否处于运行状态,再之后又会再次判断其运行状态和其中的线程数量,其源码如下
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
* 1. 如果正在运行的线程少于corePoolSize线程,请尝试使用给定命令作为其第一个任务启动
* 一个新线程。对addWorker的调用从原子上检查runState和workerCount,从而通过返回
* false来防止在不应该添加*线程的情况下产生虚假警报。
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
* 2. 如果一个任务能被成功的放入到队列中,那么我们仍然需要进行
* 一次双重校验来判断是否应该添加线程(因为有可能一个存在的线程在第一次检查后被回收了)
* 或者在进入这个方法之后线程池关闭了。因此我们重新检查了线程池的状态,并在线程池停止的
* 状态下有必要去回滚入队操作,或者在没有线程的时候去启动一个新的线程
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
* 3. 如果我们不能将这个任务放入队列中,那么我们会尝试去添加一个新的线程。如果这个操作
* 失败了,那么就表明线程池已经关闭或者是处于饱和状态,因此会拒绝该任务
*/
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);
}
}
/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked. If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
*
* 校验当前线程池状态和给定的界限(包括核心线程数和最大线程数)来确定是否能够添加一个新的
* worker。如果能被添加,workerCount则会进行相应的调整,并且如果可能的话,将会创建启动一
* 个新的线程来将fistTask作为第一个task来执行。这个方法会在线程池停止或者是能被关闭的
* 时候返回去false。并且它也会在线程工厂创建线程失败的时候返回false。如果这个线程创建
* 失败了,要么是线程工厂返回了null,要么是由于一个异常(通常是在调用Thread.start()
* 导致的OOM)导致的,都会完全回滚
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) {
return false;
}
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) {
return false;
}
if (compareAndIncrementWorkerCount(c)) {
break retry;
}
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) {
continue retry;
}
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
{
throw new IllegalThreadStateException();
}
workers.add(w);
int s = workers.size();
if (s > largestPoolSize) {
largestPoolSize = s;
}
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) {
addWorkerFailed(w);
}
}
return workerStarted;
}
看源码上面是分成3步,总结一下其流程如下:
1、先判断线程池的状态,如果线程池不在RUNNING状态,则拒绝该任务(这一步在上述代码步骤里面无法看出,实际上是整个方法的逻辑体现了如果线程池不在RUNNING状态则会拒绝该任务)。
2、如果workerCount
< corePoolSize
创建并且启动一个新的线程用于执行该任务。
3、如果workerCount
>= corePoolSize
则判断线程池状态,如果处于RUNNING且阻塞队列未满则将该任务加入到阻塞队列中。
4、如果workerCount
>= corePoolSize
&& workerCount
< maximumPoolSize
,此时如果阻塞队列已满,则创建并启动一个线程来执行该任务。
5、如果workerCount
>= maximumPoolSize
,此时如果阻塞队列已满,则根据拒绝策略来对插入的任务进行处理。流程图如下:
从上面可以知道,阻塞队列在其中时候一个很重要的部分,它为ThreadPoolExecutor提供了管理任务的能力。线程池的本质就是对任务和线程的管理,想要协调好任务和线程就需要先将这两者进行解耦,在这之后才能更好的调度任务和线程。线程池中的工作模式是生产者消费者模式,阻塞队列来缓冲任务,工作线程从阻塞队列中取出任务执行。
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
BlockingQueue
:它有多种实现,各类的实现所具有的特性都不一样,下面简单介绍下它的一些实现
实现类 | 描述 |
---|---|
ArrayBlockingQueue | 一个使用数组实现的有界阻塞队列,该队列采用先进先出进行排序。支持公平锁和非公平锁 |
DelayQueue | 使用堆实现的有界有序的阻塞队列,其中的对象只有在其设置的到期时间到了后才能被取出。ps:不能将null元素放置到这种队列中 |
LinkedBlockingDeque | 一个由双向链表实现的阻塞队列,队列的头和尾都能添加和删除元素,在并发时能够降低锁竞争 |
LinkedBlockingQueue | 由单向链表实现的阻塞队列,该队列采用先进先出进行排序,默认大小是Integer.MAX_VALUE |
LinkedTransferQueue | 由链表实现的无界阻塞队列,其中相比于其他队列,它多出了tryTransfer和transfer方法 |
PriorityBlockingQueue | 一个支持线程优先级的无界阻塞队列,默认自然进行排序,也可以通过compareTo方法来进行排序,以实现不同的线程优先级 |
SynchronousQueue | 一个不存储元素的阻塞队列,每一次put需要等待take操作,否则不允许添加元素。支持公平锁和非公平锁 |
看完阻塞队列再来看看任务是怎么执行的,从流程中可以看出任务有两种执行方式,一是直接创建新线程来执行该任务,另一种是从阻塞队列中取出一个任务进行执行,直接创建新线程来执行任务只有可能发生在线程刚创建时,其中绝大多数任务都是由线程去获取队列中的任务执行。
这一小节最后要讲的就是任务拒绝的,线程池提供了4个拒绝策略,都是ThreadPoolExecutor的内部类,如下:
策略 | 描述 |
---|---|
CallerRunsPolicy | 由调用线程(提交任务的线程)处理该任务。这种情况是能够让所有任务都得到执行的,适用于大量计算的任务类型 |
AbortPolicy | 丢弃该任务并且抛出RejectedExecutionException。这是线程池默认的拒绝策略,在任务不能再提交时技术的抛出错误,适用于比较关键的业务 |
DiscardPolicy | 丢弃任务,但是不抛出异常,对于一些不是特别重要的业务可以使用该策略 |
DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新提交被拒绝的任务 |
2.3 Worker
worker在线程池中的作用是用来控制线程状态以及线程的生命周期。它实现了Runnable接口,在其内部持有了一个线程,是用来执行任务的。同时他还持有一个fisrtTask,它可以为null,在不为null时,这个worker创建出来就会先去执行这个任务,在为null时则代表这个worker所持有的线程是非核心线程,他会去任务队列(workerQueue
)中取任务执行。
worker的执行过程:
上一节也说到绝大多数任务都是由线程去获取队列中的任务执行的,那就先看看他是怎么去实现的
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
//worker所持有的线程
final Thread thread;
//worker运行的第一个任务 可有可无
Runnable firstTask;
//这个worker执行完的任务数
volatile long completedTasks;
public void run() {
runWorker(this);
}
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted()) {
wt.interrupt();
}
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
/**
* Performs blocking or timed wait for a task, depending on
* current configuration settings, or returns null if this worker
* must exit because of any of:
* 执行阻塞后者是定时等待的任务,是取决于当前的设置,或者是worker因为一下任一原因必须退出
* 而返回null
* 1. There are more than maximumPoolSize workers (due to
* a call to setMaximumPoolSize).
* 超过了maximumPoolSize的线程(由于调用setMaximumPoolSize方法)
* 2. The pool is stopped.
* 线程池处于stop状态
* 3. The pool is shutdown and the queue is empty.
* 线程池处于shutdown状态并且队列是空的
* 4. This worker timed out waiting for a task, and timed-out
* 这个线程在等待执行任务的时间超过了设置的keepAliveTime
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) {
return null;
}
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null) {
return r;
}
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
从代码中可以看到,其中是通过worker
的run
方法执行的runWorker
方法,runWorker
方法内是通过getTask
方法来获取阻塞队列中的任务,其流程图如下:
看完getTask
方法,我们再回到runWorker
方法,我们从上面的runWorker
方法可以看到在它跑任务的时候会加上一个锁,这个锁是AQS实现的(acquire(1)
),这里加上锁是为了保证他持有的那个任务不会被中途中断,因为在shutdown
方法和tryTerminate
方法中会调用interruptIdleWorkers
方法去中断空闲的worker,在这个方法中会尝试去给需要中断的worker在加一次锁,如果成功了则说明此时处于空闲状态,可以被中断。其流程图如下:
ps:常见线程池的创建
这些线程池都是通过Executors来创建的,在阿里巴巴的开发手册是不允许使用这种方式创建线程池的,最好是自己手动创建ThreadPoolExecutor来创建线程池,因为对于Executors创建的线程池由于一些参数是固定的,导致在某些情况下可能会导致OOM。下面就对Executors提供的线程池进行一些解释,为什么不推荐使用并且为什么会导致OOM。
(1)FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
对于线程池而言,我们需要关注的重点是一下几点:
corePoolSize
:nThreads
mamximumPoolSize
:nThreads
workQueue
:LinkedBlockingQueue
由于corePoolSize和mamximumPoolSize是一样的,所以mamximumPoolSize参数是没有意义的,再到LinkedBlockingQueue,由于在创建线程池时时没有指定这个队列的大小的,而对于LinkedBlockingQueue来说如果不指定大小那这个队列的长度是Integer.MAX_VALUE,在极端情况下是可能造成OOM的
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
(2)SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
corePoolSize
:1
mamximumPoolSize
:1
workQueue
:LinkedBlockingQueue
可以看到其中corePoolSize和mamximumPoolSize都为1,所以这个线程池就是一个线程,和FixedThreadPool一样,mamximumPoolSize是不生效的,其使用的阻塞队列也是LinkedBlockingQueue,也是没有指定容量,所以可能造成OOM。
(3)CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize
:0
mamximumPoolSize
:Integer.MAX_VALUE
workQueue
:SynchronousQueue
可以看到corePoolSize设置的是为0的,而mamximumPoolSize可以看做是无限大的,而mamximumPoolSize为什么要设置成Integer.MAX_VALUE,这是与SynchronousQueue特性相关的(SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。),因为这个特性所以mamximumPoolSize要设置成Integer.MAX_VALUE,这样就能够避免频繁的出现任务拒绝,同时这也会导致OOM。
(4)ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
corePoolSize
:corePoolSize
mamximumPoolSize
:Integer.MAX_VALUE
workQueue
:DelayedWorkQueue
可以看到其中corePoolSize是在创建的时候需要指定的,而mamximumPoolSize则是默认的Integer.MAX_VALUE的,而DelayedWorkQueue队列是数据结构是最小堆,而这个堆最终又是由一个可变长的数组实现的,所以导致一个局面就是mamximumPoolSize可以无限大,而且DelayedWorkQueue也可以无限大,显然会造成OOM。
最后谈一谈对于如何选择corePoolSize
和mamximumPoolSize
下面有个简单的方法:
CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。单凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
好啦,对ThreadPoolExecutor的一些简单分析就到这了,在这内部还是有许多的内容还需要进一步的研究,例如各个阻塞队列,还有就是自己在创建线程池的如果去把握corePoolSize和mamximumPoolSize的大小。