V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yodhcn
V2EX  ›  Java

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

  •  
  •   yodhcn ·
    yodhcn · 2023-05-22 14:59:41 +08:00 · 2132 次点击
    这是一个创建于 585 天前的主题,其中的信息可能已经有所发展或是发生改变。

    用 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 提交,才开始执行。

    10 条回复    2023-05-23 11:13:34 +08:00
    hankli
        1
    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
        2
    yodhcn  
    OP
       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
        3
    cheneydog  
       2023-05-22 19:48:06 +08:00   ❤️ 1
    所以结论是啥?为啥时间还变长了?有啥优势?
    blessingsi
        4
    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
        5
    yodhcn  
    OP
       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
        6
    kawowa  
       2023-05-23 07:31:45 +08:00 via iPhone
    很有趣的发现,mark 一个
    zhiyu1998
        7
    zhiyu1998  
       2023-05-23 08:53:04 +08:00
    cool~
    blessingsi
        8
    blessingsi  
       2023-05-23 09:28:48 +08:00 via Android   ❤️ 1
    @yodhcn 在 run 方法开始的时候记录一下时间。
    ```
    var now= system.nanotime();
    sour("执行中,task" + "xxxx" + now);
    ```
    liudaolunhuibl
        9
    liudaolunhuibl  
       2023-05-23 10:23:49 +08:00   ❤️ 1
    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
        10
    blessingsi  
       2023-05-23 11:13:34 +08:00
    看到的打印的顺序和实际方法开始执行的顺序是没有一致性保证的。而且 jdk17 和 jdk8 的 sout 方法实现也变了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5444 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 08:27 · PVG 16:27 · LAX 00:27 · JFK 03:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.