本文介绍了执行parallelStream.forEach(..)时本机java代码中的NullPointerException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下异常(堆栈跟踪):

I have the following exception (the 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<Org> orgsToCaluclate, Set<Org> orgsToSendUpdate) {
    orgsToCaluclate.parallelStream().forEach(o -> {
        pnlService.updateMidPrices(o);
        Collection<SystemTradeOrder> allLiveTradesByOrgId = tradesRepository.getAllLiveTradesByOrgId(o.getId());
        updateUnrealizedPNL(o, allLiveTradesByOrgId);
    });

    // more code ....

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

So it looks like the exception is thrown inside java native code while running forEach(..) method.I mean, the NullPointerException isn't thrown from my own code - not from my consumer function which appears as the argument in forEach(..) method.

我仔细检查了 orgsToCaluclate 当这段代码运行时设置。

I double checked that there is no modification to the orgsToCaluclate set when this piece of code runs.

这是 orgsToCaluclate的初始化

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

有什么想法吗?..

推荐答案

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

We got used to say that an exception’s stack trace reflects "where it happened", but that’s an imprecise statement. An exception’s stack trace usually reflects where its instance has been created.

当我们有表单代码时,

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

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

The JRE will create an instance of NullPointerException when we try to dereference null for invoking the method length(), so its stack trace will report line 2.

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

However, when we have the following code

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

堆栈跟踪不会报告检测到错误情况的位置(行 2 )也没有抛出异常的地方(行 4 )但是在实例创建的地方,在行 3

The stack trace will not report where the erroneous condition has been detected (line 2) nor where the exception has been thrown (line 4) but where the instance has been created, in line 3.

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

For most practical cases, these places are close enough to make no significant difference, but here, we have an extraordinary situation.

正如, ForkJoinTask 将通过Reflection创建一个已经遇到的异常的新实例,我们可以看到。

As tonakai has pointed out, the ForkJoinTask will create a new instance of an already encountered exception via Reflection, as we can see in its source code when the threads mismatch.

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

When it succeeds, its stack trace will precisely reflect where the new exception instance has been created, which is in some generated code performing the reflective instance creation. Of course, this successful creation can’t be distinguished from the situation when the JRE creates an exception due to an error condition when executing the same code.

但是当我们看时更接近,我们看到整个反思创作附有

But when we look closer at the source code, we see that the entire reflective creation is enclosed with a

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

块。因此,如果操作确实失败,则不会出现任何异常。相反,代码已经倒下以返回原始异常。这表明反射代码没有失败,而是我们看到 getThrowableException() NullPointerException 实例$ c>以及后来故意抛出 ForkJoinTask 来报告在处理过程中另一个线程中有一个 NullPointerException

block. So if the operation really failed, no exception was visible. Instead, the code had fallen over to return the original exception. This indicates that the reflective code didn’t fail, but instead we see the successfully, reflectively created NullPointerException instance returned by getThrowableException() and later on deliberately throw by ForkJoinTask to report that there was a NullPointerException in another thread during the processing.

但是此代码初始化新异常的原因指向原始异常。例如。以下代码:

But this code initializes the cause of the new exception to point to the original one. E.g. the following code:

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)

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

so you are still able to recognize what happened. You just have to pay attention to the cause. Since the stack trace in your question doesn’t look like the typical Throwable.printStackTrace() output, it might be the code which produced this output which ignored the cause property of the exception.

作为附录,我们可以检查如果使用自定义异常类型真的失败了会发生什么:

As an addendum, we can check what happens if that recreation really fails using a custom exception type:

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)

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

showing that the NullPointerException thrown during the reflective recreation via the default constructor stays unreported and the original exception from the other thread is thrown directly instead.

这篇关于执行parallelStream.forEach(..)时本机java代码中的NullPointerException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-24 16:34
查看更多