トップダウン学習
まず、自分の言葉で理解することを学びます
1.1なぜスレッドプールが必要なのですか?
JVMはカーネル状態を切り替えてスレッドを作成するため、スレッドを頻繁に作成するとCPUリソースが占有され、ビジネスは解決されません。より多くのCPUリソースが、スレッドの作成と破棄ではなく、ビジネス処理に使用されることを期待しています。したがって、作成されたスレッドをキャッシュするためにプーリングのアイデアを使用してください。
1.2この要求を満たすために、スレッドプールはどのように設計されていますか?
まず、いくつかの基本的な概念を説明する必要があります。
1.2.1スレッドの説明方法-Worker
1.2.2タスクを表す方法—Runable
スレッドプールの本質はスレッドとタスクをキャッシュすることであるため、workerSet
スレッド(Worker
オブジェクト)をキャッシュするためのスレッドコレクションと、タスクworkQueue
を( Runable
)が必要です。workerSet内のスレッドは、workQueueからスレッドを継続的に取得して実行します。workQueueにタスクがない場合、ワーカーはキューにタスクが存在するまでブロックし、実行を継続するためにタスクが取り出されます。
1.3この設計の下でAPIを合理的に設計する方法は?
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
复制代码
corePoolSize
maximumPoolSize
対応するプールのサイズと対応するキャッシュスレッドの時間、対応するキャッシュの特定の実装、対応するスレッドの作成、およびキャッシュを保存できない場合の拒否ポリシー
keepAliveTime
unit
workQueue
threadFactory
handler
1.4スレッドプールがどのように機能するかを簡単に説明するにはどうすればよいですか?
タスクがスレッドプールに送信されると、次のようになります。
-
スレッドプールが現在、corePoolSizeよりも実行中のスレッドが少ないかどうか。そうである場合は、タスクを実行するための新しいワーカースレッドを作成します。両方がタスクを実行している場合は、2に進みます。
-
BlockingQueueがいっぱいかどうかを判断し、いっぱいでない場合は、スレッドをBlockingQueueに入れます。それ以外の場合は3に進みます。
-
新しいワーカースレッドを作成すると、現在実行中のスレッドの数がmaximumPoolSizeを超える場合、タスクを処理するためにRejectedExecutionHandlerに渡されます。
第二に、以下はコードの単なる説明です
2.0スレッドプールのいくつかのメンバー変数を説明する
//这个属性是用来存放 当前运行的worker数量以及线程池状态的
//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放,重写equals和hashCode方法
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;
复制代码
内部状態
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
复制代码
其中AtomicInteger变量ctl的功能非常强大: 利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态:
- RUNNING: 即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
- SHUTDOWN: ,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
- STOP : 即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
- TIDYING :,即高3位为010, 所有的任务都已经终止;
- TERMINATED:即高3位为011, terminated()方法已经执行完成
2.1 为什么需要Worker来描述线程?Worker有什么作用?
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
复制代码
分析一个类的作用,首先看他实现了那些接口。Worker继承了Aqs和Runnable。
-
继承了AQS类,可以方便的实现工作线程的中止操作;
-
实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;
-
当前提交的任务firstTask作为参数传入Worker的构造方法;
Worker的run方法执行的是线程池的runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 中断相关的代码,中断的时候统一处理
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
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 = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
复制代码
runWorker方法是线程池的核心:
-
线程启动之后,通过unlock方法释放锁,设置AQS的state为0,表示运行可中断;
-
Worker执行firstTask或从workQueue中获取任务:
-
进行加锁操作,保证thread不被其他线程中断(除非线程池被中断)
-
检查线程池状态,倘若线程池处于中断状态,当前线程将中断。
-
执行beforeExecute
-
执行任务的run方法
-
执行afterExecute方法
-
解锁操作
2.2 Worker是怎样获取任务的?
通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用cpu资源;
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.
// 线程池正在停止或者任务队列为空,则工作线程数wc减去1,然后直接返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 跑到这里说明线程池还处于RUNNING状态,重新获取一次工作线程数
int wc = workerCountOf(c);
// 超时时间控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 1.wc > maximumPoolSize说明当前的工作线程总数大于maximumPoolSize,说明setMaximumPoolSize()方法减少了线程池容量\
// 或者 2.timed && timedOut说明了线程命中了超时控制并且上一轮循环通过poll()方法从任务队列中拉取任务为null\
// 并且 3. 工作线程总数大于1或者任务队列为空,则通过CAS把线程数减去1,同时返回null,\
// CAS把线程数减去1失败会进入下一轮循环做重试
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;
}
}
}
复制代码
注意这里一段代码是keepAliveTime起作用的关键:
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
复制代码
allowCoreThreadTimeOutがfalseの場合、アイドル状態であってもスレッドは破棄されません。trueの場合、keepAliveTime内でアイドル状態のままであればスレッドは破棄されます。
スレッドが破棄されずにアイドル状態で待機できる場合timed==false、workQueue.take task:ブロッキングキューが空の場合、現在のスレッドは一時停止されて待機されます。タスクがキューに追加されると、スレッドはウェイクアップされます。 、およびtakeメソッドはタスクを返し、実行します。
スレッドが無限のアイドル時間を許可しない場合==true、workQueue.pollタスク:keepAliveTime時間内にブロッキングキューにタスクがまだない場合は、nullを返します。
2.3タスクはどのように実行されますか?
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//workerCountOf获取线程池的当前线程数;小于corePoolSize,执行addWorker创建新线程执行command任务
if (addWorker(command, true))
return;
c = ctl.get();
}
// double check: c, recheck
// 线程池处于RUNNING状态,把提交的任务成功放入阻塞队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// recheck and if necessary 回滚到入队操作前,即倘若线程池shutdown状态,就remove(command)
//如果线程池没有RUNNING,成功从阻塞队列中删除任务,执行reject方法处理任务
if (! isRunning(recheck) && remove(command))
reject(command);
//线程池处于running状态,但是没有线程,则创建线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 往线程池中创建新的线程失败,则reject任务
else if (!addWorker(command, false))
reject(command);
}
复制代码
メソッドexecuteの実装からわかるように、addWorkerは主に新しいスレッドの作成とタスクの実行を担当します。スレッドプールがタスクを実行するための新しいスレッドを作成するときは、グローバルロックを取得する必要があります。
private boolean addWorker(Runnable firstTask, boolean core) {
// CAS更新线程池数量
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
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 {
w = new Worker(firstTask);
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);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 线程启动,执行任务(Worker.thread(firstTask).start());
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
复制代码