JDK17 的 ThreadPoolExecutor 不会在 execut(runnable) 后立即执行,这是什么新特性吗?

2023-05-22 14:59:41 +08:00
 yodhcn

用 IDEA 新建了两个测试项目,一个 JDK8 ,另一个 JDK17

测试代码如下:

public class Task implements Runnable {

    private int no;
    private int size;

    public Task(int no, int size) {
        this.no = no;
        this.size = size;
    }

    @Override
    public void run() {
        try {
            System.out.println("执行中, Task: " + no + ", Thread: " + Thread.currentThread().getName() + ", queue.size: " + size);
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Main {

    private static ExecutorService pool;

    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(8);

        pool = new ThreadPoolExecutor(
                2,
                5,
                1000,
                TimeUnit.MILLISECONDS,
                queue,
                Executors.defaultThreadFactory(),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("拒绝中, " + r.toString() + ", queue.size: " + queue.size());
                    }
                });

        for (int i = 0; i < 15; i++) {
            pool.execute(new Task(i, queue.size()));
        }
        pool.shutdown();
    }
}

JDK 8 环境下的运行结果:

执行中, Task: 1, Thread: pool-1-thread-2, queue.size: 0
执行中, Task: 10, Thread: pool-1-thread-3, queue.size: 8
执行中, Task: 11, Thread: pool-1-thread-4, queue.size: 8
执行中, Task: 12, Thread: pool-1-thread-5, queue.size: 8
执行中, Task: 0, Thread: pool-1-thread-1, queue.size: 0
拒绝中, com.example.demo.Task@6d6f6e28, queue.size: 8
拒绝中, com.example.demo.Task@135fbaa4, queue.size: 8
执行中, Task: 2, Thread: pool-1-thread-4, queue.size: 0
执行中, Task: 3, Thread: pool-1-thread-3, queue.size: 1
执行中, Task: 4, Thread: pool-1-thread-1, queue.size: 2
执行中, Task: 5, Thread: pool-1-thread-2, queue.size: 3
执行中, Task: 6, Thread: pool-1-thread-5, queue.size: 4
执行中, Task: 7, Thread: pool-1-thread-4, queue.size: 5
执行中, Task: 9, Thread: pool-1-thread-3, queue.size: 7
执行中, Task: 8, Thread: pool-1-thread-1, queue.size: 6

JDK 17 环境下的运行结果:

拒绝中, demo.Task@2d98a335, queue.size: 8
拒绝中, demo.Task@4e50df2e, queue.size: 8
执行中, Task: 11, Thread: pool-1-thread-4, queue.size: 8
执行中, Task: 10, Thread: pool-1-thread-3, queue.size: 8
执行中, Task: 12, Thread: pool-1-thread-5, queue.size: 8
执行中, Task: 0, Thread: pool-1-thread-1, queue.size: 0
执行中, Task: 1, Thread: pool-1-thread-2, queue.size: 0
执行中, Task: 3, Thread: pool-1-thread-4, queue.size: 1
执行中, Task: 2, Thread: pool-1-thread-3, queue.size: 0
执行中, Task: 4, Thread: pool-1-thread-5, queue.size: 2
执行中, Task: 5, Thread: pool-1-thread-2, queue.size: 3
执行中, Task: 6, Thread: pool-1-thread-1, queue.size: 4
执行中, Task: 7, Thread: pool-1-thread-4, queue.size: 5
执行中, Task: 8, Thread: pool-1-thread-3, queue.size: 6
执行中, Task: 9, Thread: pool-1-thread-2, queue.size: 7

结论

可见 JDK17 的 ThreadPoolExecutor ,在通过 execut 提交 runnable 后,不会立即执行被提交的 runnable ,而是等待一段时间。如果在这段等待时间内没有新的 runnable 提交,才开始执行。

2109 次点击
所在节点    Java
10 条回复
hankli
2023-05-22 16:06:11 +08:00
把 jdk17 的 tpe 复制一份,在 runWorker 方法加个日志,你会发现立即执行了.并没有等待.从执行 runworker 到真正直行你 task 的 run 中间时间有点变长了

2(阶段):pool-1-thread-3Task{no=8, time=168958}
2:pool-1-thread-4Task{no=9, time=194958}
拒绝中, Task{no=11, time=-19455630845391}, queue.size: 8
2:pool-1-thread-5Task{no=10, time=194047}
3(阶段):pool-1-thread-3Task{no=8, time=394249}
2:pool-1-thread-2Task{no=1, time=614927}
3:pool-1-thread-5Task{no=10, time=350700}
拒绝中, Task{no=12, time=-19455631020344}, queue.size: 8
3:pool-1-thread-4Task{no=9, time=333429}
执行中, Task: 10, Thread: pool-1-thread-5, queue.size: 8
执行中, Task: 8, Thread: pool-1-thread-3, queue.size: 8
yodhcn
2023-05-22 16:31:08 +08:00
@hankli #1 谢谢老哥的指导。

我之后又做了个实验,写了个死循环一直调用 pool.execute(new Task(i, queue.size()))

[假设] 不会立即执行被提交的 runnable ,而是等待一段时间。如果在这段等待时间内没有新的 runnable 提交,才开始执行。
[实验] 写死循环一直调用 pool.execute(new Task(i, queue.size())),不断提交新的 runnable ,如果假设成立,被提交的 runnable 将永远不会被执行。
为了方便观察,注释掉 RejectedExecutionHandler 里的打印语句,结果在控制台发现 “执行中, Task...” 日志,与假设矛盾。

正如老哥所说的那样,只是 “从执行 runworker 到真正直行你 task 的 run 中间时间有点变长了”
cheneydog
2023-05-22 19:48:06 +08:00
所以结论是啥?为啥时间还变长了?有啥优势?
blessingsi
2023-05-22 20:52:01 +08:00
java8:
```
执行中, Task: 0, Thread: pool-1-thread-1, queue.size: 0, time: 425747251451497
执行中, Task: 1, Thread: pool-1-thread-2, queue.size: 0, time: 425747251525194
执行中, Task: 10, Thread: pool-1-thread-3, queue.size: 8, time: 425747251772510
执行中, Task: 11, Thread: pool-1-thread-4, queue.size: 8, time: 425747251892745
执行中, Task: 12, Thread: pool-1-thread-5, queue.size: 8, time: 425747251928019
拒绝中, Task@4554617c, queue.size: 8, time: 425747251889215
拒绝中, Task@74a14482, queue.size: 8, time: 425747252008173
执行中, Task: 2, Thread: pool-1-thread-3, queue.size: 0, time: 425749253444220
执行中, Task: 3, Thread: pool-1-thread-1, queue.size: 1, time: 425749254108044
执行中, Task: 4, Thread: pool-1-thread-4, queue.size: 2, time: 425749254203373
执行中, Task: 5, Thread: pool-1-thread-5, queue.size: 3, time: 425749254286334
执行中, Task: 6, Thread: pool-1-thread-2, queue.size: 4, time: 425749254359863
执行中, Task: 7, Thread: pool-1-thread-3, queue.size: 5, time: 425751254069256
执行中, Task: 8, Thread: pool-1-thread-2, queue.size: 6, time: 425751259174650
执行中, Task: 9, Thread: pool-1-thread-4, queue.size: 7, time: 425751259199900
```

java17:
```
拒绝中, Task@30f39991, queue.size: 8, time: 425953431226281
拒绝中, Task@38af3868, queue.size: 8, time: 425953447958506
执行中, Task: 10, Thread: pool-1-thread-3, queue.size: 8, time: 425953430891601
执行中, Task: 11, Thread: pool-1-thread-4, queue.size: 8, time: 425953431206209
执行中, Task: 1, Thread: pool-1-thread-2, queue.size: 0, time: 425953430740641
执行中, Task: 0, Thread: pool-1-thread-1, queue.size: 0, time: 425953430633193
执行中, Task: 12, Thread: pool-1-thread-5, queue.size: 8, time: 425953431287397
执行中, Task: 3, Thread: pool-1-thread-2, queue.size: 1, time: 425955450935401
执行中, Task: 4, Thread: pool-1-thread-5, queue.size: 2, time: 425955450944853
执行中, Task: 2, Thread: pool-1-thread-4, queue.size: 0, time: 425955450921501
执行中, Task: 6, Thread: pool-1-thread-3, queue.size: 4, time: 425955451482689
执行中, Task: 5, Thread: pool-1-thread-1, queue.size: 3, time: 425955451440625
执行中, Task: 7, Thread: pool-1-thread-2, queue.size: 5, time: 425957453093453
执行中, Task: 8, Thread: pool-1-thread-5, queue.size: 6, time: 425957453281909
执行中, Task: 9, Thread: pool-1-thread-4, queue.size: 7, time: 425957453416090
```

实际运行的时间和 sout 看到的顺序是不一样的
yodhcn
2023-05-22 21:07:40 +08:00
@blessingsi #4

“执行中, Task: 10, Thread: pool-1-thread-3, queue.size: 8, time: 425747251772510”
日志里的 time 时间戳,指的是在 ThreadPoolExecutor#runWorker 运行开始?还是在 ThreadPoolExecutor#runWorker 方法体里调用 task.run(); 之前?
kawowa
2023-05-23 07:31:45 +08:00
很有趣的发现,mark 一个
zhiyu1998
2023-05-23 08:53:04 +08:00
cool~
blessingsi
2023-05-23 09:28:48 +08:00
@yodhcn 在 run 方法开始的时候记录一下时间。
```
var now= system.nanotime();
sour("执行中,task" + "xxxx" + now);
```
liudaolunhuibl
2023-05-23 10:23:49 +08:00
jdk17 比起 jdk8 在 runworker 中真正执行你 task 的 run 方法之前新增了一个方法:clearInterruptsForTaskRun ,注释是这样的:* Ensures that unless the pool is stopping, the current thread
* does not have its interrupt set. This requires a double-check
* of state in case the interrupt was cleared concurrently with a
* shutdownNow -- if so, the interrupt is re-enabled.
blessingsi
2023-05-23 11:13:34 +08:00
看到的打印的顺序和实际方法开始执行的顺序是没有一致性保证的。而且 jdk17 和 jdk8 的 sout 方法实现也变了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/941935

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX