タスクが来ると、スレッドプールはスレッドを割り当ててタスクを処理するか、タスクをキャッシュキューに入れます。では、スレッドプールはどのようにタスクを効率的に処理するのでしょうか。複数のタスクがキャッシュキューに入り、マルチスレッドがタスクをフェッチします。これは同時実行の問題にどのように対処しますか?スレッドはステートフルですが、スレッドプールはステートフルですか?少しずつ見ていきましょう。
まず、基本的なスレッドプールの状態属性について説明します。
スレッドプールの状態:スレッドプールには5つの状態があり、これらの5つの状態はある程度カプセル化されています。
- 実行中:新しく送信されたタスクを受け入れることができ、実行中のブロッキングキュー内のタスクを処理することもできます。
- シャットダウン:閉じた状態で、新しく送信されたタスクを受け入れなくなりましたが、ブロックキューに保存されたタスクを引き続き処理できます。スレッドプールがRUNNING状態の場合、shutdown()メソッドを呼び出すと、スレッドプールはこの状態になります。前回の記事のコードで便利だと思いました。
- STOP:新しいタスクを受け入れることができず、キュー内のタスクを処理せず、タスクを処理しているスレッドに割り込むことができません。スレッドプールがRUNNINGまたはSHUTDOWN状態の場合、shutdownNow()メソッドを呼び出すと、スレッドプールがこの状態になります。これを直接呼び出さないことをお勧めします。一部のタスクが処理されない場合、例外がスローされます。
- 整理:すべてのタスクが終了した場合、workerCount(有効なスレッドの数)は0であり、スレッドプールはこの状態に入った後にterminated()メソッドを呼び出してTERMINATED状態に入ります。
- TERMINATED:terminateed()メソッドが実行された後、この状態に入ります。デフォルトでは、terminateed()メソッドでは何も実行されません。
コードを見てみましょう:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 计数位 29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 左移29位然后-1,0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 五种线程状态
//-1左移29位,也就是-536870912
//二进制展示是1010 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING = -1 << COUNT_BITS;
//二进制展示是0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//二进制展示是0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
//二进制展示是0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
//二进制展示是0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取线程状态
//~代表非,那么~CAPACITY也就是1110 0000 0000 0000 0000 0000 0000 0000
// c 假设是 1010 0000 0000 0000 0000 0000 0000 0000
// 那么返回的就是 1010 0000 0000 0000 0000 0000 0000 0000
private static int runStateOf(int c) { return c & ~CAPACITY; }
スレッドプールはctlを使用して、スレッドプールの実行ステータスとスレッドプール内の有効なスレッドの数を制御します。スレッドセーフなAtomicIntegerが使用されます。Ctlには合計32ビットがあります。最初の3ビットはステータスビットです。最後の29ビットはスレッド数のカウントです。つまり、2 ^ 29-1 = 536870912であり、5億を超えています。これは非常に多く、スレッド数が多すぎると推定されます。保持できません。あまり使用しないでください。CPUはGG、O(∩_∩)Oはは〜です。
スレッドの状態は、-1,0,1,2,3だけ左にシフトされた29桁の数字で表されていることがわかります。注:-1,0,1,2,3になることはありません。あなたはそう言う、あなたは私がソースコードを読んでいないことを知っている。私はそれをちらっと見て話し始めた。。泥棒は恥ずかしい
次に、ステータスを取得するときに、c&〜CAPACITYを介して直接取得でき、数量もctlを介して直接取得できます。このように、デザインは良いサンプルペーパーですか?ふふ
操作を見てみましょう。
public void execute(Runnable command) {
// 运行的对象都没有,只能抛出异常了
if (command == null)
throw new NullPointerException();
// 获取ctl的value,线程状态和workerCount都在里面,没它不行啊
int c = ctl.get();
//上面说了获取线程状态。workerCountOf(c) 这个就是获取线程数量了
//小于核心线程数,那就分配线程直接运行呗
if (workerCountOf(c) < corePoolSize) {
// 添加到任务
if (addWorker(command, true))
return;
//添加任务失败,这个时候ctl也许会改变,需要重新获取ctl
c = ctl.get();
}
//如果当前线程是运行状态并且添加任务到列表成功
if (isRunning(c) && workQueue.offer(command)) {
// 获取ctl
int recheck = ctl.get();
//再次判断线程池的运行状态,如果不是运行状态,把刚刚加到workQueue中的command移除
if (! isRunning(recheck) && remove(command))
// 拒绝策略
reject(command);
//有效线程数为0,那就要执行addWork方法了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//再次调用addWorker方法,但第二个参数传入为false,
//将线程池的有限线程数量的上限设置为maximumPoolSize;
else if (!addWorker(command, false))
reject(command);
}
上記はスレッドプールで実行されているタスクを実行する主な方法であり、簡単なコメントが書かれています。このコードは主に3つのステップに分かれています。
- 実行中のスレッドの数がcorePoolSize未満の場合は、スレッドを直接使用してタスクを実行します
- タスクが正常にキューに入れられた場合でも、チェックしたばかりのスレッドが実行された可能性があるため、タスクがスレッドに参加する必要があるかどうかをチェックする必要があります。
- キャッシュキューに入ることができない場合、キャッシュキューはいっぱいになり、スレッドを再起動することしかできません。最大スレッドが許可されている場合、スレッドの開始に失敗すると、タスクは拒否されるだけです。
コアメソッドaddWorker〜を見てみましょう。
private boolean addWorker(Runnable firstTask, boolean core) {
//retry 这个是标志位,就是在循环中continue,break的时候可以进行多层跳出
//个人觉得这段代码使用while处理可能更亲切一点,哈哈
retry:
// 无限循环,不多说
for (;;) {
//获取ctl以及状态
int c = ctl.get();
int rs = runStateOf(c);
// 线程池的状态是int类型,从运行到关闭是逐渐增大的,所以直接使用大于小于对比
// rs >= SHUTDOWN,表示此时不再接收新任务
// rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
// 既然不接受新任务,那 firstTask当然要为空
// 如果workQueue不为空,就是处理workQueue里面的任务,没问题,但是加了一个否定,那还处理啥?
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
for (;;) {
// 工作的线程的计数,就是第几个工作的线程
int wc = workerCountOf(c);
// 超出最大限制(默认2亿多,这个是可以初始化设置的,不会真是这么多)还搞啥?GG,
// 根据core判断,是取corePoolSize还是maximumPoolSize,超过了线程数目,当然有也GG
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 尝试增加workerCount,成功的话就跳出循环
if (compareAndIncrementWorkerCount(c))
//跳出retry下面的这个循环
break retry;
// 重新获取ctl
c = ctl.get();
// 获取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 {
// 新建一个worker
w = new Worker(firstTask);
// 获取worker的线程
//这个ThreadFactory如果没设置的话,会默认使用默认java.util.concurrent.Executors.DefaultThreadFactory
final Thread t = w.thread;
//线程肯定不能为空,为空的话那就增加任务失败,执行不了呗
if (t != null) {
//获取新的ReentrantLock锁(排它锁)
final ReentrantLock mainLock = this.mainLock;
//加锁,不多说哈,不熟悉的可以看下之前锁的讲解
// 因为这是一个线程池,肯定存在多线程竞争的情况,比如同时去取一个任务,同时去执行等等
mainLock.lock();
try {
//获取ctl状态
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN表示是RUNNING
// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,继续下面的逻辑
// SHUTDOWN状态下,不会添加新的任务,只会执行缓存列表中的任务
if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {
//预检查t是否可启动
if (t.isAlive())
throw new IllegalThreadStateException();
// private final HashSet<Worker> workers = new HashSet<Worker>();
// workers是一个HashSet
workers.add(w);
// largestPoolSize 线程池的最大线程数量
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 任务增加成功,也分配到线程了
if (workerAdded) {
// 那就启动线程,运行任务呗
t.start();
workerStarted = true;
}
}
} finally {
// 启动失败的话,就把已经加入到workers里面的任务移除掉
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorkerにはfirstTaskとcoreの2つの入力パラメーターしかありません。つまり、addworker(タスクの追加)はタスクとスレッドプールのサイズのみを考慮し、スレッドプールのサイズはコアから渡されたパラメーターに従って決定されます。
このメソッドに入るには、最初に状態をフィルタリングします。スレッドプールには5つの状態があります。RUNNINGとSHOWDOWNを除いて、他の状態はタスクを実行しなくなり、SHUTDOWN状態はキャッシュキュー内のタスクのみを実行するため、addWorker最初にこれを判断しました。
次に、スレッド数の判断です。スレッド数は、メソッドで許可されている最大スレッド数を超えてはなりません。最大スレッド数を超えると、タスクを処理できないスレッドになります。直接GGし、falseを返します。
もちろん、shutdownの呼び出しやshutdownNowメソッドの呼び出しなど、実行の途中で状態が変化した場合は、forループを再度実行する必要があります。
上記のチェックで問題がなかったので、タスクの追加を開始します。
新しいタスクを作成し、スレッドを取得してから、排他ロックであるロックを追加します。スレッドプールはマルチスレッドであるため、存在して象を検出し、ワーカーのHashSetはスレッドセーフではないため、ロックする必要があります。
ロックが追加されると、ワーカーがワーカーに正常に追加されます。つまり、タスクが正常に追加され、後で開始できます。
起動時は直接t.start()です。このtがどのように発生したかがわかります。tは労働者から取られ、労働者の工法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
this.thread = getThreadFactory().newThread(this); 这个线程中的runnable,其实就是传进来的firstTask!所以t.strat(),也就是运行firstTask。
大致的流程就是这样哈。
ほぼ完成です。次回も見てみましょう〜
犠牲も勝利もありません〜