为什么用线程池
有时候,系统需要处理非常多的执行时间很短的请求,如果每一个请求都开启一个新线程的话,系统就要不断的进行线程的创建和销毁,有时花在创建和销毁线程上的时间会比线程真正执行的时间还长。而且当线程数量太多时,系统不一定能受得了。
使用线程池主要为了解决一下几个问题:
● 通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。
● 对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。
继承关系
public class ThreadPoolExecutor extends AbstractExecutorService {
构造函数
//用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的 ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue)
{
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//用给定的初始参数和默认被拒绝的执行处理程序创建新的 ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
{
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
//用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
{
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
//用给定的初始参数创建新的 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;
}
参数说明:
corePoolSize - 核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
maximumPoolSize - 线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime - 非核心线程的闲置超时时间,超过这个时间就会被回收。
unit - keepAliveTime - 指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
threadFactory - 线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
线程管理机制图:
Executors创建线程池
1、构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,
同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,
不会由RejectedExecutionHandler处理
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2、构造一个缓冲功能的线程池,配置corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,
keepAliveTime=60s,以及一个无容量的阻塞队列 SynchronousQueue,因此任务提交之后,
将会创建新的线程执行;线程空闲超过60s将会销毁
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3、构造一个只支持一个线程的线程池,配置corePoolSize=maximumPoolSize=1,
无界阻塞队列LinkedBlockingQueue;保证任务由一个线程串行执行
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4、构造有定时功能的线程池,配置corePoolSize,无界延迟阻塞队列DelayedWorkQueue;有意思的是:
maximumPoolSize=Integer.MAX_VALUE,由于DelayedWorkQueue是无界队列,所以这个值是没有意义的
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
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, TimeUnit.NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
线程池如何提交任务
1.execute()方法提交
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。通过以下代码可知execute()方法输入的任务是一个Runnable类的实例。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/**
1.如果工作线程小于核心线程池数量,尝试新建一个工作线程执行任务addWorker。
addWorker将会自动检查线程池状态和工作线程数,以防在添加工作线程的过程中,
线程池被关闭。
2.如果创建工作线程执行任务失败,则任务入队列,如果入队列成功,
我们仍需要二次检查线程池状态,以防在入队列的过程中,线程池关闭。
如果线程池关闭,则回滚任务。
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)
//如线程池已关闭,且工作线程为0,则创建一个空闲工作线程
addWorker(null, false);
}
/**根据最大线程池数量,判断是否应该添加工作线程,
如果当前工作线程数量小于最大线程池数量,则尝试添加
工作线程线程执行任务,如果尝试失败,则拒绝任务处理 */
else if (!addWorker(command, false))
reject(command);
}
具体看看其中的addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();//获取当前线程状态
int rs = runStateOf(c);
/**
判断逻辑成立可以分为以下几种情况均不接受新任务
1.rs > shutdown 不接受新任务
2.rs >= shutdown && firstTask != null 不接受新任务
3.rs >= shutdown && workQueue.isEmpty 不接受新任务
判断逻辑成立
1.rs == shutdown && firstTask != null 此时不接受新任务,但会执行队列中的任务
2.rs == shutdown && firstTask == null 会执行addworker(null,false)
防止shutdown状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况
添加一个null任务是因为shutdown状态下,线程池不再接受新任务
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//如果线程池状态为running并且队列中还有需要执行的任务
for (;;) {
//获取线程池中线程数量
int wc = workerCountOf(c);
//如果超出容量或者最大线程池容量不在接受任务
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//线程安全增加工作线程数
if (compareAndIncrementWorkerCount(c))
//跳出retry
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 {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
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();
}
//启动新添加的线程,限制性firstTask,然后不停的从队列中循环读取任务执行
if (workerAdded) {
//执行ThreadPoolExecutor的runWorker方法启动线程
t.start();
workerStarted = true;
}
}
} finally {
//线程启动失败,则从workers中溢出w并且递减workerCount
if (! workerStarted)
//递减workerCount会触发tryTerminate方法
addWorkerFailed(w);
}
return workerStarted;
}
2.submit()方法提交
submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
如何终止线程池的2种方法
1.shutdown()方法
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
/**
上锁,mainLock是线程池的主锁,是可重入锁,当要操作workers set这个保持线程的HashSet时,
需要先获取mainLock*/
mainLock.lock();
try {
//判断调用者是否有权限shutdown线程池
checkShutdownAccess();
//CAS+循环设置线程池状态为shutdown,如果至少是这个状态,那么直接返回
advanceRunState(SHUTDOWN);
//中断所有空闲线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}
2.shutdownNow()方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//STOP状态不接受新的任务,也不执行队列中的任务
advanceRunState(STOP);
//中断所有线程
interruptWorkers();
//返回队列中没有被执行的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
两种方法的区别
shutDown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
shutdownNow()
执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。