为什么慢速任务会阻止该程序中使用并行流的其他较小任务?

时间:2019-01-28 17:50:56

标签: java multithreading java-8 java-stream

在一个同事关于并行流的问题之后,我编写了以下代码来测试某些东西。

public class Test {

    public static void main(String args[]) {
        List<Runnable> list = new LinkedList<>();
        list.add(() -> {
            try {
                Thread.sleep(10000);
                System.out.println("Time : " + System.nanoTime() + " " + "Slow task");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        for (int i = 0; i < 1000; i++) {
            int j = i;
            list.add(() -> System.out.println("Time : " + System.nanoTime() + " " + j));
        }
        list.parallelStream().forEach(r -> r.run());
    }
}

奇怪的是,输出始终类似于以下内容。

Time : 4096118049370412 61
Time : 4096118049567530 311
Time : 4096118049480238 217
Time : 4096118049652415 405
Time : 4096118049370678 436
Time : 4096118049370575 155
Time : 4096118049720639 437
Time : 4096118049719368 280
Time : 4096118049804630 281
Time : 4096118049684148 406
Time : 4096118049660398 218

TRUNCATED  

Time : 4096118070511768 669
Time : 4096118070675678 670
Time : 4096118070584951 426
Time : 4096118070704143 427
Time : 4096118070714441 428
Time : 4096118070722080 429
Time : 4096118070729569 430
Time : 4096118070736782 431
Time : 4096118070744069 432
Time : 4096118070751286 433
Time : 4096118070758554 434
Time : 4096118070765913 435
Time : 4096118070550370 930
Time : 4096118070800538 931
Time : 4096118070687425 671
Time : 4096118070813669 932
Time : 4096118070827794 672
Time : 4096118070866089 933
Time : 4096118070881358 673
Time : 4096118070895344 934
Time : 4096118070907608 674
Time : 4096118070920712 935
Time : 4096118070932934 675
Time : 4096118070945131 936
Time : 4096118070957850 676
Time : 4096118070982326 677
Time : 4096118070991158 678
Time : 4096118070999002 679
Time : 4096118071006501 680
Time : 4096118071017766 681
Time : 4096118071025766 682
Time : 4096118071033318 683
Time : 4096118071070603 684
Time : 4096118071080240 685
Time : 4096128063025914 Slow task
Time : 4096128063123940 0
Time : 4096128063148135 1
Time : 4096128063173285 2
Time : 4096128063176723 3
Time : 4096128063179939 4
Time : 4096128063183077 5
Time : 4096128063191001 6
Time : 4096128063194156 7
Time : 4096128063197273 8
Time : 4096128063200395 9
Time : 4096128063203581 10
Time : 4096128063206988 11
Time : 4096128063210155 12
Time : 4096128063213285 13
Time : 4096128063216411 14
Time : 4096128063219542 15
Time : 4096128063222733 16
Time : 4096128063232190 17
Time : 4096128063235653 18
Time : 4096128063238827 19
Time : 4096128063241962 20
Time : 4096128063245176 21
Time : 4096128063248296 22
Time : 4096128063251444 23
Time : 4096128063254557 24
Time : 4096128063257705 25
Time : 4096128063261566 26
Time : 4096128063264733 27
Time : 4096128063268115 28
Time : 4096128063272851 29

Process finished with exit code 0

也就是说,即使所有其他任务都已完成,总会有一些任务在等待慢速任务完成处理。我假设慢速任务应该只占用一个线程,而其他所有任务应该都没有问题地完成,并且只有慢速任务应该占用整整10秒钟。我有8个CPU,所以并行度为7。

这可能是什么原因?

要添加更多信息,该代码仅用于理解目的。我不会把它放在生产中的任何地方。

1 个答案:

答案 0 :(得分:2)

There are some limited capabilities when it comes to work-stealing with streams,因此,如果一个线程将自己钉在其他跑步者的某些工作上,则该工作将被阻塞,直到完成其他任务的处理为止。

您可以通过在代码周围添加更多少量调试说明来实现此目的...

class Test {

    public static void main(String[] args) {
        List<Runnable> list = new LinkedList<>();
        list.add(() -> {
            try {
                System.out.println("Long sleep - " + Thread.currentThread().getName());
                Thread.sleep(10000);
                System.out.println("Time : " + System.nanoTime() + " " + "Slow task");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        for (int i = 0; i < 1000; i++) {
            int j = i;
            list.add(() -> System.out.println("Time : " + System.nanoTime() + " " + j));
        }
        list.parallelStream().forEach(r -> {
            System.out.println(Thread.currentThread().getName());
            r.run();
            System.out.println();
        });
    }
}

运行此命令后,我看到以下消息:

Long sleep - ForkJoinPool.commonPool-worker-4

...大约十秒钟后...

Time : 11525122027429 Slow task

ForkJoinPool.commonPool-worker-4
Time : 11525122204035 0

ForkJoinPool.commonPool-worker-4
Time : 11525122245739 1

ForkJoinPool.commonPool-worker-4
Time : 11525122267015 2

ForkJoinPool.commonPool-worker-4
Time : 11525122286921 3

ForkJoinPool.commonPool-worker-4
Time : 11525122306266 4

ForkJoinPool.commonPool-worker-4
Time : 11525122338787 5

ForkJoinPool.commonPool-worker-4
Time : 11525122357288 6

ForkJoinPool.commonPool-worker-4
Time : 11525122376716 7

ForkJoinPool.commonPool-worker-4
Time : 11525122395218 8

ForkJoinPool.commonPool-worker-4
Time : 11525122414165 9

ForkJoinPool.commonPool-worker-4
Time : 11525122432755 10

ForkJoinPool.commonPool-worker-4
Time : 11525122452805 11

ForkJoinPool.commonPool-worker-4
Time : 11525122472624 12

ForkJoinPool.commonPool-worker-4
Time : 11525122491380 13

ForkJoinPool.commonPool-worker-4
Time : 11525122514417 14

ForkJoinPool.commonPool-worker-4
Time : 11525122534550 15

ForkJoinPool.commonPool-worker-4
Time : 11525122553751 16

因此,这意味着在我的盒子上,worker-4计划进行一些工作,基于存在一些不均匀块的事实,该工作无法被盗。注意:如果线程正在分块地处理任务,则该工作将不会进一步分解。

[31, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 32, 31, 32, 0]

如果您正在寻找可以从运行时间更长的线程中窃取工作的线程实现,则最好直接使用工作窃取池。

class Test {

    public static void main(String[] args) throws InterruptedException {
        List<Runnable> list = new LinkedList<>();
        list.add(() -> {
            try {
                System.out.println("Long sleep - " + Thread.currentThread().getName());
                Thread.sleep(10000);
                System.out.println("Time : " + System.nanoTime() + " " + "Slow task");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        for (int i = 0; i < 1000; i++) {
            int j = i;
            list.add(() -> {
                System.out.println(Thread.currentThread().getName());
                System.out.println("Time : " + System.nanoTime() + " " + j);
                System.out.println();
            });
        }


        final ExecutorService stealingPool = Executors.newWorkStealingPool();
        list.forEach(stealingPool::execute);
        stealingPool.shutdown();
        stealingPool.awaitTermination(15, TimeUnit.SECONDS);
    }
}

以上内容在列表末尾显示了更稳定,更合理的结果:

Time : 12210445469314 Slow task

...。这意味着所有可用的工作已在分配的时间(15秒)内处理完毕。