JDK 线程池里真的区分 核心线程与非核心线程吗?

JDK 线程池里真的区分 核心线程与非核心线程吗?

不少校招小伙伴对于线程池的了解,大概就是如下图:

  • 当核心线程数未满,则新建核心线程执行任务
  • 当核心线程数满了,队列未满,则将任务放在等待队列里,等待核心线程去执行
  • 当核心线程数满了(但未达最大线程数),队列也满了,新建非核心线程执行任务
  • 如果已经达到最大线程数,且队列也满了,则执行饱和策略。

img

但是这样的了解,可能是不够的。因为这只是一个基础的流程,稍微问细一点、深一点就不够用了。比如,我可能会问下面这些问题:

  • 都是知道核心线程默认是不销毁的,那么核心线程在队列中没有任务时它是什么状态(线程状态),对于操作系统来说会不会分配时间片给它?
  • 我们一般自己新创建一个线程,可能使用 new Thread,然后 执行一下 start 方法,然后这个线程 执行完run方法内代码,就销毁了。线程池 ThreadPoolExecutor 中是如何实现 线程复用的?
  • 线程池中 区分 核心线程与非线程线程吗?他们数据结构上以及行为上有什么不同。
  • 非核心线程是不是只执行 新提交的任务,不消费等待队列中的任务。

这里我详细说说问题3:线程池中 区分 核心线程与非线程线程吗?

实际上从源代码上看,无论是 数据结构还是行为上,其根据没有字段标记这个 线程是核心线程还是非核心线程。线程被包装在一个 Work对象中。这个 Work对象中 包含一个 Thread对象和 一个Runnable对象以及一些其他变量。但是并没有变量去区分这个 work对象是 所谓核心,还是非核心所以说数据结构上是一致的。

源码中唯一 带有是否核心的变量 就是 下面这个 core。但是它的作用,用于检查 线程数是否超出限制。

因为线程池有两个扩容Work的时机:一个是初始时核心线程的扩容,一个是非核心线程的扩容。

1
private boolean addWorker(Runnable firstTask, boolean core)

当核心线程扩容时,用的是 corePoolSize;非核心线程扩容时,用的是maximumPoolSize。

但是在Work运行后,其内部从队列中取任务或者销毁时,并不知道自己当初添加的时候是 当作核心线程来扩容的还是非核心线程扩容的。

我们可以看源码中,final void runWorker(Worker w) 中:

有一个for循环。如果task为空,并且从队列中取不到任务,则结束for循环,进入到 负责清理 Worker和管理线程状态的processWorkerExit方法里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
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);
}
}

简单的说:每个Work对象 它是不知道自己的身份的,无论是他获取队列中任务,还是销毁的判断条件。都不依赖于它创建是当作核心线程创建的,还是非核心线程。所以说他们的行为是没有区别的。

判断一个work是否会因为超时销毁,只看 allowCoreThreadTimeOut(是否允许核心线程超时销毁) 和 wc > corePoolSize (当前线程是否超过核心线程数)。只要两个满足其一,就可能因为超时销毁。

1
2
3
4
5
6
7
8
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}