通过《线程池内运行的线程抛异常,线程池会怎么办》了解到当线程执行的任务出现异常时,会将当前线程移出线程池,并新增一个线程到线程池中,我们先来回顾一下线程池的运行原理:
从原理图中可以看到只有当队列满了之后,才会去创建新的线程执行新加入的任务,那么到底有没有可能出现队列未满, 但是运行中的线程个数大于核心线程数?
理论上应该是不可能大于核心线程数,那么有没有意外呢?答案暂时不揭晓,我们先往下看,写几个demo测试一下
1 | public static void main(String[] args) { |
运行结果
第一列为线程hashcode,第二列为阻塞队列中的任务个数,第三列为计算数字
从控制台的输出能看到一共出现了4个不同的hashcode,也就表示创建过4个线程,然后线程1869318657执行任务时出现了异常,也就是除0异常,然后将其从线程池中移出,后续不再进行任务处理。从之前的文章中我们知道将异常的线程移除之后会重新创建一个线程加入到线程池中,那么正常情况下,线程池移除一个再加入一个,数量应该不变,但是从下面的输出看到这时同时有3个线程在处理任务,但是我们的核心线程数是2,为什么会出现3?
我们再来看下任务发生异常时的后续处理代码:
1 | private void processWorkerExit(Worker w, boolean completedAbruptly) { |
我们看到在创建新线程时,会进行一系列判断,若判断全部通过,就会去给线程池的池子中创建一个新的工作线程,这里传入的core参数是false,也就是当成非核心线程来创建,难道问题出在这里?如果我此时运行的线程数小于等于核心线程数,那么再加进来一个非核心线程?我们来看看addWorker()方法的代码:
1 | private boolean addWorker(Runnable firstTask, boolean core) { |
从方法addWorker()的源码中我们看到,当我们参数core传一个false进来时,会去校验当前工作中的线程个数是否超过了最大线程数,如果没有超过的话则创建新的线程,问题就出在这里,为什么?
在方法processWorkerExit()中,我们对核心线程数进行的比较,但是如果在①处有新的任务调用execute()进来,则发现工作线程未达到核心线程数,这个时候就会去创建一个核心线程,创建之后,线程池中的工作线程就有2个了,然后我们从processWorkerExit()中传过来的请求会去校验工作线程个数是否超过了最大线程数,在我们的demo代码中是肯定不会超过的,那么就又会去创建一个线程放到线程池中,这样线程池中的工作线程就有3个了,具体流程图如下
正常流程 | 异常流程 | 工作线程数 |
---|---|---|
execute() | 0 | |
—addWorker() | 1 | |
execute() | 1 | |
—addWorker() | 2 | |
processWorkerExit() | 2 | |
—remove() | 1 | |
execute() | 1 | |
—addWorker() | 2 | |
—addWorker() | 3 |
这就是为什么我们核心线程数是2,但是最终运行中的线程个数是3的原因
🌰场景
-
任务尚未全部进入到队列中,队列未满
1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));
return t;
});
for (int i = 0; i < 30; i++) {
int finalI = i;
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", " + (10 / finalI));
});
pool.execute(t);
}
}这个也就是我们上面跑的demo,工作线程个数超出了核心线程数。
-
任务已经全部进入到队列中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));
return t;
});
for (int i = 0; i < 30; i++) {
int finalI = i;
Thread t = new Thread(() -> {
if (finalI < 2) {
try {
// 前两个线程等待500毫秒,保证所有的任务都进入到队列中
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
System.out.println(
Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", " + (10 / (finalI - 4)));
});
pool.execute(t);
}
}我们看到当线程池开始执行任务时,已经把剩余的28个任务都放到队列中了,红色和蓝色下划线的线程是初始线程,当873出现异常后,将其移出线程池,然后创建一个新的线程执行队列中的剩余任务,因为这时所有的任务都在队列中等待被执行,execute()方法不会被调用,所以这种场景下,工作中的线程个数不会超过核心线程数。
-
任务尚未全部进入到队列中,队列已满,未达到最大线程数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 10, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(30), r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.hashCode() + ", " + t1.getName() + " " + "发生了异常"));
return t;
});
for (int i = 0; i < 34; i++) {
int finalI = i;
Thread t = new Thread(() -> {
if (finalI < 2) {
try {
// 等待任务全部进入到队列中,且创建完额外线程接收多出来的两个任务
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().hashCode() + ": " + pool.getQueue().size() + ", "
+ pool.getActiveCount() + ", " + (10 / (finalI - 33)));
});
pool.execute(t);
}
}运行结果
第一列为线程hashcode,第二列为阻塞队列中的任务个数,第三列为当前的工作线程个数,第四列为计算数字
从结果中我们看到创建了4个工作线程去执行任务,当线程230执行异常之后,创建了线程734继续执行,线程个数未超过最大线程数
-
任务尚未全部进入到队列中,队列已满,达到最大线程数
这种情况就会依据拒绝策略执行相关的代码逻辑,线程数不会超过最大线程数
总结
从以上分析我们可以得出,只有在:任务尚未全部进入到队列中且队列未满的情况下,才会出现工作线程个数大于核心线程数,所以我们在使用线程池的过程中,待执行的任务尽量捕获所有的异常情况,不要将其抛出到线程池中的线程里。