在前面的文章中介绍过如何去创建一个线程,这个比较简单,那么会有个问题,如果创建的线程多了,会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间
那么如何让现有的线程复用呢?那就是通过线程池来达到这个效果!
首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。
一、ThreadPoolExecutor类
ThreadPoolExecutor是java.uitl.concurrent(简称JUC java并发工具包)下面的类,是线程池中最核心的一个类。
在ThreadPoolExecutor类中提供了四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
1、参数的含义
1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
一般队列有以下几种选择:
ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;
6、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
7、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
拒绝策略一般有以下几种,当然也可以自定义拒绝策略(实现RejectedExecutionHandler接口):
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
2、结构解析
ThreadPoolExecutor继承AbstractExecutorService
AbstractExecutorService实现ExecutorService接口
ExecutorService继承Executor
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
void shutdown();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果
二、线程池实现原理图
1、流程图
2、执行图
三、线程池的具体实现原理
1.线程池的生命周期
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个生命周期的阶段:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
private static int ctlOf(int rs, int wc) { return rs | wc; }
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
- RUNNING
这是线程池的初始状态。此状态下线程池会接受新任务并且处理队列中等待的任务。 - SHUTDOWN
RUNNING状态下调用shutdown方法后进入此状态。此状态下线程池不接受新任务,但会处理队列中等待的任务。 - STOP
RUNNING/SHUTDOWN状态下调用shutdownNow方法后进入此状态。此状态下线程池不接受新任务,也不处理既有等待任务,并且会中断既有运行中的线程。 - TIDYING
SHUTDOWN/STOP状态会流转到此状态。此时所有任务都已运行完毕,工作线程数为0,任务队列都为空。从字面角度理解,此时线程池已经清干净了。 - TERMINATED
TIDYING状态下,线程池执行完terminated钩子方法后进入此状态,此时线程池已完全终止。
2、ThreadPoolExecutor的成员变量
private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小
//、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集
private volatile long keepAliveTime; //线程存货时间
private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize; //线程池最大能容忍的线程数
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数
3、execute()方法
在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可:
/**
* execute方法可以说是线程池中最核心的方法,
* 在继承链上层的AbstractExecutorService中将各种接受新任务的方法最终转发给了此方法进行任务处理。
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 分类讨论:
* 1. 如果当前线程数<核心线程数,则会开启一个新线程来执行提交的任务。
*
* 2. 尝试向任务队列中添加任务。这时需要再次检查方法开始到当前时刻这段间隙,
* 线程池是否已经关闭了/线程池中没有工作线程了。
* 如果线程池已经关闭了,需要在任务队列中移除先前提交的任务。
* 如果没有工作线程了,则需要添加一个空任务工作线程用于执行提交的任务。
*
* 3. 如果无法向阻塞队列中添加任务,则尝试创建一个新的线程执行任务。
* 如果失败,回调饱和策略处理任务。
*/
int c = ctl.get();
// 线程数 < corePoolSize
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 检查线程池是否处于运行状态,并向任务队列中添加任务
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
/*
* 再次检查是否线程池处于运行状态,如果不是则移除任务并回调饱和策略拒绝任务。
* 因为有可能上面if条件读到线程池处于运行状态,而后shutdown/shutdownNow方法被调用,
* 这时候需要把尝试刚才加入任务队列中的任务移除。
*/
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果workerCount为0,需要添加一个工作线程用于执行提交的任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 添加一个新的工作线程处理任务。
* 如果失败,则说明线程池已经关闭或者已经饱和了,此时回调饱和策略来拒绝任务。
*/
else if (!addWorker(command, false))
reject(command);
}
4、addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/*
* 如果线程池状态至少为STOP,返回false,不接受任务。
* 如果线程池状态为SHUTDOWN,并且firstTask不为null或者任务队列为空,同样不接受任务。
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
/*
* CAPACITY为(1<<29)-1,这是线程池中线程数真正的上界,绝不允许超过。
* 因为ThreadPoolExecutor设计中是用低29位表示工作线程数的。
*
* 否则根据参数中是否以corePoolSize为上界进行判断,如果超过,则新增worker失败。
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 成功新增workCount,跳出整个循环往下走。
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
/*
* 重读总控状态,如果运行状态变了,重试整个大循环。
* 否则说明是workCount发生了变化,重试内层循环。
*/
if (runStateOf(c) != rs)
continue retry;
}
}
// 运行到此处时,线程池线程数已经成功+1,下面进行实质操作。
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 {
// 由于获取锁之前线程池状态可能发生了变化,这里需要重新读一次状态。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 向工作线程集合添加新worker,更新largestPoolSize。
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 成功增加worker后,启动该worker线程。
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// worker线程如果没有成功启动,回滚worker集合和worker计数器的变化。
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
5、addWorkerFailed
在新增工作线程失败的情况下,调用addWorkerFailed:
- 从worker集合删除失败的worker。
- workCount减1。
- 调用tryTerminate尝试终止线程池。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
6、Worker类的实现
private final class Worker implements Runnable {
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
volatile long completedTasks;
Thread thread;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
boolean isActive() {
return runLock.isLocked();
}
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) {
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
void interruptNow() {
thread.interrupt();
}
/**
* 工作线程运行核心逻辑。
* 简单来说做的事情就是不断从任务队列中拿取任务运行。
*/
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
// 把firstTask设置为null,从GC角度来看,这处代码很重要。
w.firstTask = null;
// 置互斥锁状态为0,此时可以被中断。
w.unlock();
// 用于标记完成任务时是否有异常。
boolean completedAbruptly = true;
try {
// 循环:初始任务(首次)或者从阻塞阻塞队列里拿一个(后续)。
while (task != null || (task = getTask()) != null) {
/*
* 获取互斥锁。
* 在持有互斥锁时,调用线程池shutdown方法不会中断该线程。
* 但是shutdownNow方法无视互斥锁,会中断所有线程。
*/
w.lock();
/*
* 这里if做的事情就是判断是否需要中断当前线程。
* 如果线程池至少处于STOP阶段,当前线程未中断,则中断当前线程;
* 否则清除线程中断位。
*
* if条件中的Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)
* 做的事情说穿了就是清除中断位并确认目前线程池状态没有达到STOP阶段。
*/
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, 计数器+1, 释放互斥锁。
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
/*
* 处理工作线程退出。
* 上面主循环中的前置处理、任务调用、后置处理都是可能会抛出异常的。
*/
processWorkerExit(w, completedAbruptly);
}
}
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this); //当任务队列中没有任务时,进行清理工作
}
}
}
它实际上实现了Runnable接口,因此上面的Thread t = threadFactory.newThread(w);效果跟下面这句的效果基本一样:
Thread t = new Thread(w);
相当于传进去了一个Runnable任务,在线程t中执行这个Runnable。
既然Worker实现了Runnable接口,那么自然最核心的方法便是run()方法了
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
从run方法的实现可以看出,它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去取新的任务来执行,那么去哪里取呢?自然是从任务缓存队列里面去取,getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法,下面是getTask方法的实现:
/**
* 工作线程从任务队列中拿取任务的核心方法。
* 根据配置决定采用阻塞或是时限获取。
* 在以下几种情况会返回null从而接下来线程会退出(runWorker方法循环结束):
* 1. 当前工作线程数超过了maximumPoolSize(由于maximumPoolSize可以动态调整,这是可能的)。
* 2. 线程池状态为STOP (因为STOP状态不处理任务队列中的任务了)。
* 3. 线程池状态为SHUTDOWN,任务队列为空 (因为SHUTDOWN状态仍然需要处理等待中任务)。
* 4. 根据线程池参数状态以及线程是否空闲超过keepAliveTime决定是否退出当前工作线程。
*/
private Runnable getTask() {
// 上次从任务队列poll任务是否超时。
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/*
* 如果线程池状态已经不是RUNNING状态了,则设置ctl的工作线程数-1
* if条件等价于 rs >= STOP || (rs == SHUTDOWN && workQueue.isEmpty())
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
/*
* allowCoreThreadTimeOut是用于设置核心线程是否受keepAliveTime影响。
* 在allowCoreThreadTimeOut为true或者工作线程数>corePoolSize情况下,
* 当前工作线程受keepAliveTime影响。
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* 1. 工作线程数>maximumPoolSize,当前工作线程需要退出。
* 2. timed && timedOut == true说明当前线程受keepAliveTime影响且上次获取任务超时。
* 这种情况下只要当前线程不是最后一个工作线程或者任务队列为空,则可以退出。
*
* 换句话说就是,如果队列不为空,则当前线程不能是最后一个工作线程,
* 否则退出了就没线程处理任务了。
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 设置ctl的workCount减1, CAS失败则需要重试(因为上面if中的条件可能不满足了)。
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 根据timed变量的值决定是时限获取或是阻塞获取任务队列中的任务。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// workQueue.take是不会返回null的,因此说明poll超时了。
timedOut = true;
} catch (InterruptedException retry) {
// 在阻塞队列上等待时如果被中断,则清除超时标识重试一次循环。
timedOut = false;
}
}
}
7、processWorkerExit方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
/*
* 因为正常退出,workerCount减1这件事情是在getTask拿不到任务的情况下做掉的。
* 所以在有异常的情况下,需要在本方法里给workCount减1。
*/
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 累加completedTaskCount,从工作线程集合移除自己。
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 由于workCount减1,需要调用tryTerminate方法。
tryTerminate();
int c = ctl.get();
// 只要线程池还没达到STOP状态,任务队列中的任务仍然是需要处理的。
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
/*
* 确定在RUNNING或SHUTDOWN状态下最少需要的工作线程数。
*
* 默认情况下,核心线程不受限制时影响,
* 在这种情况下核心线程数量应当是稳定的。
* 否则允许线程池中无线程。
*/
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果任务队列非空,至少需要1个工作线程。
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 无需补偿工作线程。
if (workerCountOf(c) >= min)
return;
}
// 异常退出或者需要补偿一个线程的情况下,加一个空任务工作线程。
addWorker(null, false);
}
}
8、线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
9、线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
10、任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
四、示例
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class ThreadPoolExecutorTest {
@Test
public void threadPoolExecutorTest(){
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 100, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<10;i++){
TestTask testTask = new TestTask(i);
executor.execute(testTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
class TestTask implements Runnable {
private int taskNum;
public TestTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
}
测试结果:
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:1,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:2,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:3,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:4,已执行玩别的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:4,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行玩别的任务数目:0
正在执行task 0
正在执行task 1
正在执行task 2
正在执行task 8
从执行结果可以看出,当线程池中线程的数目大于3时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。
不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
下面是这三个静态方法的具体实现;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
有个问题就是如果超大量的请求的话,上面三个方法就不适用了,前面两个队列是无界队列,一直往里面加会撑爆内存,最后这个因为是无限制增加线程也会耗尽CPU
五、如何合理配置线程池的大小
1)、CPU密集型
这种任务我们要尽量使用较小的线程池,一般是Cpu核心数+1
因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销
2)、IO密集型
方法一:可以使用较大的线程池,一般CPU核心数 * 2
IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间
方法二:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。