美文网首页
静态代码块中的parallelStream导致的死锁

静态代码块中的parallelStream导致的死锁

作者: 黄云斌huangyunbin | 来源:发表于2019-04-22 21:38 被阅读0次

    发现一个线程一直卡住不动,看了下堆栈,是在等parallelStream的结束,奇怪是为什么会一直卡着呢,按道理一个parallelStream很快就结束的啊。

    再仔细一看,写代码的人有点作,parallelStream的代码是在静态代码块,这个最后发现就是罪魁祸首。

    写了个简单的代码,重现了问题,和大家一起分享下:

    public class ParallelTest {
    
    
        static {
            List<String> data = new ArrayList<>();
            for (int i = 0; i < 2; i++) {
                data.add("===" + i);
            }
            List<String> collect = data.parallelStream().map(ParallelTest::m).collect(Collectors.toList());
        }
    
        public static String m(String e) {
            System.out.println(Thread.currentThread() + "====" + e);
            return e + "---";
        }
    
    
        public static void main(String[] args) {
            System.out.println("end");
        }
    }
    

    结果的输出是这样的:
    Thread[main,5,main]=======1

    就一直卡着了,也不会输出end。

    这个是为什么呢?
    我们看看堆栈:

    "ForkJoinPool.commonPool-worker-1" - Thread t@12
       java.lang.Thread.State: RUNNABLE
        at com.vip.luna.dataconfig.ParallelTest$$Lambda$1/1374677625.apply(Unknown Source)
        at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
        at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
        at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
        at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
        at java.util.stream.AbstractTask.compute(AbstractTask.java:316)
        at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
        at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
        at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
        at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1689)
        at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
    
       Locked ownable synchronizers:
        - None
    
    "main" - Thread t@1
       java.lang.Thread.State: WAITING
        at java.lang.Object.wait(Native Method)
        - waiting on <7b05abd> (a java.util.stream.ReduceOps$ReduceTask)
        at java.util.concurrent.ForkJoinTask.externalAwaitDone(ForkJoinTask.java:334)
        at java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:405)
        at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:734)
        at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:714)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
        at com.vip.luna.dataconfig.ParallelTest.<clinit>(ParallelTest.java:15)
    
       Locked ownable synchronizers:
        - None
    

    这里发现一个奇怪的现象,ForkJoinPool.commonPool-worker-1这个线程是RUNNABLE的,而且堆栈就在我们要执行的方法,为什么就一直执行不完呢,我们要执行的方法没有任何的阻塞啊。

    其实,java线程的RUNNABLE,并不一定是正在执行,也可能是准备好了,等待执行。
    1 main线程等待ForkJoinPool.commonPool-worker-1线程

    ForkJoinPool.commonPool-worker-1执行完,外部类的静态代码块才能执行完,main线程才能往下走


    image.png
    2 ForkJoinPool.commonPool-worker-1线程等待main线程

    因为ForkJoinPool要执行lamda,说白了就是这个类的一个内部类,这个类正在有main线程完成初始化。

    这样就是两个线程互相等待,造成死锁了。

    要注意的点:
    可以看到2个任务其实是执行了一个的,Thread[main,5,main]=======1,那是因为java的并发流,会把当前mian线程算成并发线程池的一个线程,所以main线程的这一部分是没问题的。
    同理,如果任务只有一个,也是不会阻塞的,因为直接分给当前的main线程了。

    如何解决这个问题:

    解决方法1不使用并发流,使用普通的流就好了。

    普通的流就是在当前线程执行,自然就没有多线程的死锁问题了。

    解决方法2 lamda放到其他的类里面去,自然也不会有死锁的问题了。
    public class A {
    
        public static String m(String e) {
            System.out.println(Thread.currentThread() + "====" + e);
            return e + "---";
        }
    }
    
    
    
    public class ParallelTest {
    
        static {
            List<String> data = new ArrayList<>();
            for (int i = 0; i < 2; i++) {
                data.add("===" + i);
            }
            List<String> collect = data.parallelStream().map(A::m).collect(Collectors.toList());
        }
    
        public static void main(String[] args) {
            System.out.println("end");
        }
    }
    

    对于这个问题,之前就有人给jdk官方提过bug:
    https://bugs.openjdk.java.net/browse/JDK-8143380
    但是jdk官方认为这个不是bug,是使用的人自己的问题,就关闭了这个问题,哈哈。

    相关文章

      网友评论

          本文标题:静态代码块中的parallelStream导致的死锁

          本文链接:https://www.haomeiwen.com/subject/rwfpgqtx.html