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