执行parallelStream.forEach(..)时本机java代码中的NullPointerException

我有以下exception(stacktrace):

java.lang.NullPointerException at sun.reflect.GeneratedConstructorAccessor171.newInstance(Unknown Source) ~[?:?] at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[?:1.8.0_40] at java.lang.reflect.Constructor.newInstance(Constructor.java:422) ~[?:1.8.0_40] at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598) ~[?:1.8.0_40] at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677) ~[?:1.8.0_40] at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735) ~[?:1.8.0_40] at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160) ~[?:1.8.0_40] at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:174) ~[?:1.8.0_40] at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233) ~[?:1.8.0_40] at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) ~[?:1.8.0_40] at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:583) ~[?:1.8.0_40] at com.tradair.tnet.services.trades.TradeService.updateUnrealizedPNL(TradeService.java:173) ~[tnet.jar:5.1.1.0-SNAPSHOT] 

从我的TradeService类开始:

  public void updateUnrealizedPNL(Set orgsToCaluclate, Set orgsToSendUpdate) { orgsToCaluclate.parallelStream().forEach(o -> { pnlService.updateMidPrices(o); Collection allLiveTradesByOrgId = tradesRepository.getAllLiveTradesByOrgId(o.getId()); updateUnrealizedPNL(o, allLiveTradesByOrgId); }); // more code .... 

所以看起来在运行forEach(..)方法时抛出exception会在java本机代码中抛出。 我的意思是, NullPointerException不会从我自己的代码中抛出 – 而不是来自我的消费者函数,它在forEach(..)方法中显示为参数。

我仔细检查了这段代码运行时orgsToCaluclate集没有修改。

这是orgsToCaluclate的初始化:

  Set orgsToCaluclate = getMarginOrgs(); orgsToCaluclate = orgsToCaluclate.stream() .filter(org -> !isOrgInCloseout(org.getId())).collect(Collectors.toSet()); 

有什么想法吗?..

我们习惯于说exception的堆栈跟踪反映了“它发生的地方”,但这是一个不精确的陈述。 exception的堆栈跟踪通常反映其实例的创建位置

当我们有表格的代码时,

 1 String s=null; 2 s.length(); 

当我们尝试取消引用null以调用方法length() ,JRE将创建一个NullPointerException实例,因此它的堆栈跟踪将报告第2行。

但是,当我们有以下代码时

 1 String s=null; 2 if(s == null) { 3 RuntimeException rt=new NullPointerException(); 4 throw rt; 5 } 

堆栈跟踪不会报告检测到错误条件的位置(第2行),也不会报告引发exception的位置(第4行),而是在第3行中报告实例的创建位置。

对于大多数实际案例来说,这些地方足够接近,没有显着差异,但在这里,我们有一个特殊的情况。

正如tonakai指出的那样 , ForkJoinTask将通过Reflection创建一个已经遇到的exception的新实例,正如我们在线程不匹配时可以在其源代码中看到的那样 。

成功时,其堆栈跟踪将精确反映新exception实例的创建位置,这是在执行reflection实例创建的某些生成代码中。 当然,这种成功的创建无法与JRE在执行相同代码时由于错误条件而创建exception的情况区分开来。

但是当我们仔细观察源代码时 ,我们会看到整个reflection创建都附带了一个

 584 try { … 604 } catch (Exception ignore) { 605 } 

块。 因此,如果操作确实失败,则不会出现任何exception。 相反,代码已经倒下以返回原始exception。 这表明reflection代码没有失败,而是我们看到成功的,reflection性地创建了由getThrowableException()返回的NullPointerException实例,后来故意抛出ForkJoinTask来报告在处理期间另一个线程中存在NullPointerException

但是这段代码初始化了新exception的原因 ,指向原始exception。 例如以下代码:

 import java.util.stream.IntStream; public class Main { public static void main(String[] args) { Thread main=Thread.currentThread(); IntStream.range(0, 1000).parallel().forEach(i -> { if(Thread.currentThread()!=main) throw new NullPointerException(); }); } } 

版画

 Exception in thread "main" java.lang.NullPointerException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598) at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677) at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735) at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160) at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:189) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233) at java.util.stream.IntPipeline.forEach(IntPipeline.java:404) at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:560) at Main.main(Main.java:7) Caused by: java.lang.NullPointerException at Main.lambda$main$0(Main.java:9) at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205) at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110) at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291) 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:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 

所以你仍然能够识别发生的事情。 你只需要注意原因 。 由于问题中的堆栈跟踪看起来不像典型的Throwable.printStackTrace()输出,因此可能是产生此输出的代码忽略了exception的cause属性。


作为附录,我们可以检查如果使用自定义exception类型重新创建失败会发生什么:

 import java.util.stream.IntStream; public class Main { public static class CustomException extends RuntimeException { public CustomException() { System.err.println("will deliberately fail"); throw new NullPointerException(); } private CustomException(String message) { super(message); } } public static void main(String[] args) { Thread main=Thread.currentThread(); IntStream.range(0, 1000).parallel().forEach(i -> { if(Thread.currentThread()!=main) throw new CustomException("forced failure"); }); } } 

将打印

 will deliberately fail Exception in thread "main" Main$CustomException: forced failure at Main.lambda$main$0(Main.java:18) at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205) at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110) at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291) 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:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 

显示通过默认构造函数在reflection重新创建期间抛出的NullPointerException保持未报告,而直接抛出来自另一个线程的原始exception。

查看堆栈跟踪,看起来代码中发生exception,并且它正在尝试获取该exception的新实例,但由于NullPointerException而无法执行此操作,如果调用任何代码,是否可以检查代码在foreach中抛出任何exception并确保这些exception的所有构造函数都是正确的

  at java.lang.reflect.Constructor.newInstance(Constructor.java:422) ~[?:1.8.0_40] at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598) ~[?:1.8.0_40] at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677) ~[?:1.8.0_40] at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735) ~[?:1.8.0_40]