前言:
由于互联网的发展,多线程技术应用的越来越多,但是一味的创建线程,会带来性能消耗,而且到一定数量后,有可能使服务器崩溃,由此引进了线程池。
一、线程池的优点
- 合理的利用线程资源,从而减少线程创建和销毁的次数,降低开销。
- 根据实际情况合理的调整线程池的参数,可以一定程度上提高系统的吞吐量,提高效率。
二、线程池的创建
现在以java.util.concurrent包中ThreadPoolExecutor类为例来展示。
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
具体参数的含义:
-
corePoolSize:核心线程数,即使空闲,也不会被回收。
-
maximumPoolSize:最大线程数。
-
keepAliveTime:当线程数大于线程池中的核心线程数时,非核心线程数能存活的最长时间。
-
unit:时间单位,可选的单位有:DAYS(天),HOURS(小时),MINUTES(分钟),MILLISECONDS(毫秒),MICROSECONDS(微妙)和NANOSECONDS(纳秒)。
-
workQueue:存放任务的队列。BlockingQueue是一种带锁的阻塞队列,常见的队列分为:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue。
- SynchronousQueue:同步队列。对于工作队列,一个好的默认选择是SynchronousQueue,它将任务交给线程,而不需要持有它们。在同步队列中,如果没有立即可用的线程来运行任务,那么对任务排队的尝试将失败,因此将构造一个新线程。由于以上特性,SynchronousQueue线程容量没有限制,以防止新提交的任务被拒绝。Executors.newCachedThreadPool使用了这个队列。
- LinkedBlockingQueue:基于链表的无界阻塞队列。当线程池中核心线程满了后,新任务将一直在队列中等待。因此核心线程数等于最大线程数。(maximumPoolSize参数设置了也不会生效。)这种情况适用于每个任务都是独立的,不会相互影响。在web服务器中,如果应用LinkedBlockingQueue,可以很好的缓冲突然间的大量请求。Executors.newFixedThreadPool()使用了这种队列。
- ArrayBlockingQueue:基于数组的有界阻塞队列。可以防止资源耗尽,但是队列的大小和线程池的大小很难去权衡。
-
threadFactory:当执行程序创建新线程时,使用该工厂。threadFactory分为两种:DefaultThreadFactory和PrivilegedThreadFactory。
- DefaultThreadFactory:创建一个同线程组且默认优先级的线程,ThreadPoolExecutor默认采用这个。
- PrivilegedThreadFactory:使用访问权限创建一个权限控制的线程。
-
RejectedExecutionHandler :饱和策略,任务超出线程池容量和队列大小时执行的策略,分为:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy。
- AbortPolicy:不处理,直接抛出异常,默认策略。
- CallerRunsPolicy:将重试添加当前的任务,重复调用execute()方法。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:直接不处理,也不抛出异常。
三、线程池的运行原理
我们调用ThreadPoolExecutor类的execute方法,就可以将自己的运行任务(必须继承Runnable接口)放入线程池中运行。
执行步骤主要有三步:
-
若线程池中的线程数少于核心线程数,将创建新的线程来执行当前任务。
调用的addWorker方法将检查线程池的运行状态、工作线程数
和新的线程是否创建成功。 -
若线程池中的线程数大于核心线程数时,队列还没有满,将当前任务加入列中。
此时我们将再次检查是否需要新增一个线程(上次检查后存在死亡的线程)
或者进入此方法后,线程池关闭了。
所以我们重新检查状态,如果线程池停止了就回滚队列,并且拒绝当前任务 ;如果没有线程就启动一个新的线程。 -
在以上的基础上,如果线程池满了,但线程数少于最大线程数,将创建新的线程;如果超出了最大线程数,将采取拒绝策略。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//1.如果少于核心线程数,创建新的线程。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.核心线程已满,但任务队列没满,将加入队列中。
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);
}
//3.若核心线程和任务队列都已饱和,将尝试着创建新的线程。
else if (!addWorker(command, false))
//当线程数达到最大线程数时,采取饱和策略。
reject(command);
}
接下来就看下里面的一个重要方法addWorker,看下它的实现原理:
/**
* 检查是否可以根据当前池状态和给定的界限(核心或最大值)添加新worker。
* @param firstTask 待执行的人物
* @param core 是否处于核心线程池的数量下
* /
private boolean addWorker(Runnable firstTask, boolean core) {
retry://循环标记
for (;;) {
//获取当前线程池及运行状态
int c = ctl.get();
int rs = runStateOf(c);
// 若线程池已关闭或者运行任务为空,直接返回false
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
//当前线程数大于最大线程数时,返回false
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//线程数+1,通过CAS保证线程安全,此时退出循环
if (compareAndIncrementWorkerCount(c))
break retry;
//若以上的CAS操作+1没有成功,继续循环
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 {
//持有锁时将再次检查线程池的状态
//为了预防ThreadFactory创建失败的情况
//或者在锁获取到之前线程池已经关闭的情况
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) //预先检查线程t是否可启动
throw new IllegalThreadStateException();
//将当前任务worker加入工作组中
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;
}
参考博客:
线程池的原理