日常开发中, 如果我们需要使用线程执行某些业务, 在阿里开发手册中有明确约定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。下面我们来分析以下线程池ThreadPoolExecutor的原理及优点
线程池的优点
- 可以控制线程并发数量
- 减少线程的创建和销毁过程,提升效率
- 可以对线程进行管理
构造方法及参数说明
corePoolSize 核心线程个数
maximumPoolSize 最大工作线程个数,如果核心线程不够用,阻塞队列已满,则会增加工作线程数至此值,提高效率
keepAliveTime 增加的工作线程,如果超过这个时间后一直空闲,则取消该线程
unit 空闲时间单位
workQueue 阻塞队列,如果核心线程已满,使用队列存放等待线程 阻塞队列源码
threadFactory
handler 如果等待队列已满,并且工作线程数达到了maximumPoolSize,则再添加线程时,会调用此接口中rejectedExecution方法
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;
}
4种拒绝策略
CallerRunsPolicy 交由调用线程完成执行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
AbortPolicy 直接抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
DiscardPolicy 不进行操作
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
DiscardOldestPolicy 将队列中阻塞时间最长的线程去掉
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
submit和execute区别
execute只能接收Runnable类型参数并且没有返回值
submit可以接受Runnable类型和Callable类型参数,可以获取到线程的返回值
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); // 获取ctl的值, 其中高3位表示状态, 低29位表示工作线程worker数量
if (workerCountOf(c) < corePoolSize) { // worker数量低于核心线程数量
if (addWorker(command, true)) // 创建核心线程worker
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { // 执行到此说明核心线程已满, 添加到阻塞队列
int recheck = ctl.get(); // 再次取ctl值, 校验状态
if (! isRunning(recheck) && remove(command)) // 如果不是运行中状态, 则从阻塞队列中删除
reject(command); // 调用拒绝策略
else if (workerCountOf(recheck) == 0) // 如果worker的数量为0
addWorker(null, false); // 添加空的worker
}
else if (!addWorker(command, false)) // 执行到此说明阻塞队列添加失败, 可能已经满了, 创建弹性线程
reject(command); // 如果没有成功, 则执行拒绝策略
}
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
如何关闭
shutdownNow执行后会立即中断执行的线程,并返回阻塞队列中等待的线程。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 标记线程池状态为STOP
advanceRunState(STOP);
// 中断执行中的线程
interruptWorkers();
// 将阻塞队列中的线程以list形式返回给调用者
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted(); // 调用已启动线程的interrupt方法
} finally {
mainLock.unlock();
}
}
shutdown执行后
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 标记线程池状态为SHUTDOWN
advanceRunState(SHUTDOWN);
// 终止那些还没有运行的线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) { // 没被终止 并且 能获取到锁的线程
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
Executors构造线程池及坑点
常用的创建线程方法:
Executors.newFixedThreadPool();
Executors.newCachedThreadPool();
Executors.newSingleThreadExecutor();
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
可以看到线程队列使用的参数类型大都是LinkedBlockingQueue,而LinkedBlockingQueue默认长度是Integer.MAX_VALUE,如果阻塞队列过大,堆空间不断上升,可能会导致OOM。
这点在阿里开发手册也有相关约束:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
如何合理的设置线程池参数
线程池最关键的参数是maximumPoolSize,线程最大并发数量,如果设置过大,并发量太高,导致CPU、内存、磁盘资源用尽;如果设置过小,队列太长,不能最大限度使用硬件资源。目前并发线程数有如下通用方式(通过资源占用方式区分),以供参考:
CPU密集型
1.线程多偏于CPU运算,此时maximumPoolSize应为CPU核心数+1
IO密集型
2.可以根据生产服务运行情况,不断调整参数,达到最优,一般值为CPU核心数*2
3.通用配置CPU核心数/(1-阻塞系数) 阻塞系数返回0.8~0.9