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

2019-04-22  本文已影响0人  黄云斌huangyunbin

发现一个线程一直卡住不动,看了下堆栈,是在等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,是使用的人自己的问题,就关闭了这个问题,哈哈。

上一篇下一篇

猜你喜欢

热点阅读