1.背景
线程是写程序过程中处理并发和异步的利器,为了提高资源的利用率,一般都会使用线城池重复利用线程资源(每一次使用线程都向CPU申请线程,申请过程会使用中断,将程序由用户态切换到内核态,然后由内核给线程分配相应线程资源,是一种极大的浪费);
2.线程池的基本知识
2.1 线程池的主要参数
java 中的线程池主要使用类ThreadPoolExecutor,构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize: 核心线程数量
- maximumPoolSize: 线程池拥有的最大worker的数量
- keepAliveTime: 最大空闲生存时间
- unit: keepAliveTime的单位
- workQueue: 线程池的阻塞队列,用户排队或缓存任务
- threadFactory: 线程的工厂类,构造线程
- handler:拒绝策略,当队列已满,线程池不能再继续新增woker数量时,执行拒绝策略;ThreadPoolExecutor中的默认实现有四种; AbortPolicy, DiscardOldestPolicy,DiscardPolicy, CallerRunsPolicy,根据名称可以很容知道具体的实现,默认是 AbortPolicy:抛出 RejectedExecutionException 异常;
2.2 execute方法
int c = ctl.get(); // 线程池状态和线程数量的结合
if (workerCountOf(c) < corePoolSize) { // 若是当前线程数量 < 核心线程数
if (addWorker(command, true)) // 向线程池中添加worker,并且执行command 任务
return; // 若是成功,那么执行返回;有可能失败,因为多线程添加,到当前线程添加的时候,可能核心线程已经满了
c = ctl.get(); // 添加worker失败;此时需要再次获取当前线程池的状态 c
}
if (isRunning(c) && workQueue.offer(command)) { //判断当前线程池的状态是否为Running && 当前队列中是否能插入任务,如果不能插入,则会返回fasle
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) // 再次检查线程状态,如果不是runing,则移除掉刚刚插入的元素(任务)
reject(command); // 执行拒绝策略(防止多线程时,线程池被调用了shutdown(), 导致线程terminated 方法
else if (workerCountOf(recheck) == 0) // 若是线程池中已经不存在worker了
addWorker(null, false);
}
else if (!addWorker(command, false)) // 队列中不能再添加任务,则执行添加worker,若是不能添加,执行拒绝策略
reject(command);
2.3 addworker 方法
总结功能是,新建Worker,并且将新建的worker添加到workers队列中,然后启动worker;
核心代码如下:
...省略
w = new Worker(firstTask); //新建一个Worker
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // 将新建的worker加入workers队列中
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 如果worker添加成功,启动线程执行任务
workerStarted = true;
}
}
...省略
2.4 Worker
Worker是Thread和Task的包装,构造方法如下,注意thread中传入的任务是this,也就是当前Worker
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 将this作为线程的接收参数
}
线程启动会调用Worker#run()方法
主要逻辑是循环调用getTask()从阻塞队列中获取Task,然后触发任务执行
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
...
// 两种情况:1.任务初始化,task != null,直接进入循环执行任务;
// 之后都是从 getTask() 获取任务执行;当前线程抛出异常时会向上抛异常,并且结束当前线程;
//当新的任务进来时,又通过ThreadFactory生成新的Thread,组装成Worker放入workerQueue
while (task != null || (task = getTask()) != null) {
w.lock();
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 = null;
w.completedTasks++;
w.unlock();
}
}
ThreadPoolExecutor#getTask()
核心线程会卡在 workerQueue.take(),一直拿任务,非核心线程会是用poll(Timeout, TimeUnit),拿不到就放弃了?
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
3.遇到的问题
设置参数 corePoolSize = 200, maxPoolSize = 200, workerQueue = LinkedBockingQueue(1); 由以上分析,知道如果当前缓存的worker数量已经达到200,则会进入队列,然后worker线程将任务从队列中取出来执行;任务取出和任务加入是在不同线程中,由于任务队列较小,所以一次性提交任务多个,worker线程还没有来得及取出任务;则会查看maxPoolSize=200,说明无法再申请新的线程执行任务,导致线程池执行拒绝错误;所以你会发现一个奇怪的现象,线程池存在空闲的线程,但依旧会执行抛出任务拒绝错误