池化技术,典型有数据库连接池,线程池,宝贵对象池。无论线程,还是数据库连接,都是一种宝贵的资源,创建还是销毁都需要耗费很多时间。对宝贵资源进行池化的目的:
- 减少资源创建销毁时间。
- 防止大量创建资源造成资源耗尽,程序崩溃。
- 复用资源。
实现java线程池的有哪几种方式呢?
- 自己手动编写。可以初始化一个容器,里边事先初始化指定数量的线程。
- ThreadPoolExecutor,jdk并发包里边自带的线程池框架。
- Executors类当中封装的几个静态方法。其实就是对ThreadPoolExecutor的一个封装,本质上和上一种是一样的。
现在我们来看一下ThreadPoolExecutor的工作原理。要想明白ThreadPoolExecutor的工作原理,我们首先要看一下ThreadPoolExecutor的构造方法的几个参数:
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:工作线程从Queue中取得任务的限制时间。如果超过限制时间直接返回null。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列。
- threadFactory:用于创建工作线程的线程工厂。可以用来自定义工作线程的一些属性,比如用于标示线程的名字。
- handler:饱和策略。
上面几个参数从字面上都好理解,但是事实上我们很多情况下对这几个参数理解的不够透彻。下面我们看如下几个问题?
1.核心线程在什么时候,什么情况下创建?
是不是一开始就会初始化corePoolSize个WorkerThread呢?其实不是,默认情况下,我们构造一个ThreadPoolExecutor的时候,是不会创建任何工作线程的。那么在什么情况下会创建工作者线程呢?我们还是从代码上看!
构建方式一:
通过我们主动调用下面这个方法,它会构建corePoolSize个核心工作线程。
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
构建方式二:
当我们往线程池里边提交任务的时候,如果线程池中当前的Worker线程数小于核心线程数,那么就会创建一个核心线程。
2.当Worker线程数已经等于corePoolSize的时候,什么时候开始继续扩充创建线程?
是不是当当前核心线程都处于工作状态,然后再提交任务就会马上创建Worker线程呢?答案是不是的。还是让我们先看jdk代码:
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.
*
* 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.
*
* 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.
*/
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);
}
上诉代码是ThreadPoolExecutor提交任务的方法。上边的代码很容易理解。从上边的代码我们可以看出来:
如果当前线程数已经大于corePoolSize的话,ThreadPoolExecutor首先是把任务放到我们创建的队列当中的。
并没有添加Worker线程。那么什么情况下才会再次创建线程呢?
- 如果当前工作线程数量已经大于了核心线程数并且任务队列已经满了,那么会创建一个工作线程。
- 如果当前工作线程数量已经大于核心线程数量,任务队列也没满,但是当线程池检查当前存活的线程数量已经是0了。那么也会创建一个工作线程。
这里jdk为什么要这样做,而不是在核心线程全部被占用的时候,马上就创建新工作线程呢?我的猜想是,可能为了尽量避免少创建线程吧。毕竟创建销毁线程是要花费比较大的代价的。
那么这里我有一个问题,如果我们的任务队列是一个无界队列呢?那是不是意味着线程池的最大线程数就是核心线程数呢?
3.什么时候饱和策略会被调用呢?
如果当前任务队列已经满了,并且线程数已经到达maximumPoolSize,那么就会执行饱和策略了,也就是这个任务被线程池丢弃了。