如何修复由 DecimalFormat.format 上的同步问题导致的 NullPointerException

How to fix NullPointerException caused by synchronization issue on DecimalFormat.format

提问人:Wirling 提问时间:9/27/2019 最后编辑:Wirling 更新时间:9/28/2019 访问量:341

问:

我正在使用 DecimalFormat 类格式化 BigDecimal,但我有很多崩溃报告。我自己无法重现这个问题,但是我知道 DecimalFormat 不是线程安全的,我可能在某处犯了一个错误。

我只是不知道导致此问题的原因是什么,因为我确信BigDecimal不是null,并且每次我都会创建一个新的DecimalFormat实例。有什么我忽略了吗?

我唯一能想到的就是使方法同步。但我不确定它是否有帮助,我宁愿不使用它,因为它会减慢速度。

这是崩溃之一:

java.lang.RuntimeException: 
  at java.lang.reflect.Constructor.newInstance0 (Constructor.java)
  at java.lang.reflect.Constructor.newInstance (Constructor.java:343)
  at androidx.loader.content.ModernAsyncTask$3.done (ModernAsyncTask.java:164)
  at java.util.concurrent.FutureTask.finishCompletion (FutureTask.java:383)
  at java.util.concurrent.FutureTask.setException (FutureTask.java:252)
  at java.util.concurrent.FutureTask.run (FutureTask.java:271)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at java.lang.Thread.run (Thread.java:764)
Caused by: java.lang.NullPointerException: 
  at java.math.BigInt.putLittleEndianInts (BigInt.java:175)
  at java.math.BigInteger.getBigInt (BigInteger.java:322)
  at java.math.BigInteger.toString (BigInteger.java:856)
  at java.math.BigDecimal.toString (BigDecimal.java:2204)
  at android.icu.text.DigitList_Android.set (DigitList_Android.java:724)
  at android.icu.text.DecimalFormat_ICU58_Android.format (DecimalFormat_ICU58_Android.java:1202)
  at android.icu.text.DecimalFormat_ICU58_Android.format (DecimalFormat_ICU58_Android.java:1187)
  at java.text.DecimalFormat.format (DecimalFormat.java:709)
  at java.text.DecimalFormat.format (DecimalFormat.java:634)
  at java.text.Format.format (Format.java:157)
  at com.myapp.formatter.Format.formatAmount (Format.java:230)
  at com.myapp.formatter.MyContentProvider.query (MyContentProvider.java:123)
  at android.content.ContentProvider.query (ContentProvider.java:1057)
  at android.content.ContentProvider.query (ContentProvider.java:1149)
  at android.content.ContentProvider$Transport.query (ContentProvider.java:241)
  at android.content.ContentResolver.query (ContentResolver.java:802)
  at android.content.ContentResolver.query (ContentResolver.java:752)
  at androidx.core.content.ContentResolverCompat.query (ContentResolverCompat.java:81)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:63)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:41)
  at androidx.loader.content.AsyncTaskLoader.onLoadInBackground (AsyncTaskLoader.java:307)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$532ebdd5 (AsyncTaskLoader.java:60)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$42af7916 (AsyncTaskLoader.java:48)
  at androidx.loader.content.ModernAsyncTask$2.call (ModernAsyncTask.java:141)
  at java.util.concurrent.FutureTask.run (FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at java.lang.Thread.run (Thread.java:764)

这是代码:

public static String formatAmount(BigDecimal bigDecimal, boolean includeCents, boolean includeCurrency)
{
    String formattedAmount = "";
    if (bigDecimal != null)
    {
        DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(getLocale());
        DecimalFormat decimalFormat;
        if (includeCurrency)
        {
            decimalFormat = new DecimalFormat("¤\u00A0#,##0.00;¤\u00A0-#,##0.00", decimalFormatSymbols);
        }
        else
        {
            decimalFormat = new DecimalFormat("#,##0.00;-#,##0.00", decimalFormatSymbols);
        }

        int fractionDigits = includeCents ? 2 : 0;
        bigDecimal.setScale(fractionDigits, BigDecimal.ROUND_DOWN);

        decimalFormat.setMinimumFractionDigits(fractionDigits);
        decimalFormat.setMaximumFractionDigits(fractionDigits);

        // exception happens on next line.
        formattedAmount = decimalFormat.format(bigDecimal);
    }
    return formattedAmount;
}

编辑:我不知道为什么我的问题被标记为重复。这并不是说我不知道什么是NullPointer或如何修复它。 首先,我确定我的变量和参数不是 null。 调试或日志记录不是一个选项,因为我无法重现此问题。 最后,NullPointer 甚至不是由我的代码引起的。 所以我认为这真的是一个我无法解决的独特情况。

我还遇到了引发 nullpointer 的 Android 代码。我认为值得一提的是,大多数崩溃都发生在华为设备上。我发现的另一个区别是,某些 Android 版本的堆栈跟踪是不同的。第一个堆栈跟踪适用于搭载 Android 9 的设备。这是较低 Android 版本(7、8.0 和 8.1)上的堆栈跟踪:

java.lang.RuntimeException: 
  at java.lang.reflect.Constructor.newInstance0 (Constructor.java)
  at java.lang.reflect.Constructor.newInstance (Constructor.java:334)
  at androidx.loader.content.ModernAsyncTask$3.done (ModernAsyncTask.java:164)
  at java.util.concurrent.FutureTask.finishCompletion (FutureTask.java:383)
  at java.util.concurrent.FutureTask.setException (FutureTask.java:252)
  at java.util.concurrent.FutureTask.run (FutureTask.java:271)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:636)
  at java.lang.Thread.run (Thread.java:784)
Caused by: java.lang.NullPointerException: 
  at java.math.BigInt.putLittleEndianInts (BigInt.java:173)
  at java.math.BigInteger.getBigInt (BigInteger.java:322)
  at java.math.BigInteger.toString (BigInteger.java:856)
  at java.math.BigDecimal.toString (BigDecimal.java:2204)
  at android.icu.text.DigitList.set (DigitList.java:721)
  at android.icu.text.DecimalFormat.format (DecimalFormat.java:1187)
  at android.icu.text.DecimalFormat.format (DecimalFormat.java:1172)
  at java.text.DecimalFormat.format (DecimalFormat.java:658)
  at java.text.DecimalFormat.format (DecimalFormat.java:592)
  at java.text.Format.format (Format.java:157)
  at com.myapp.formatter.Format.formatAmount (Format.java:230)
  at com.myapp.formatter.MyContentProvider.query (MyContentProvider.java:123)
  at android.content.ContentProvider.query (ContentProvider.java:1059)
  at android.content.ContentProvider.query (ContentProvider.java:1151)
  at android.content.ContentProvider$Transport.query (ContentProvider.java:243)
  at android.content.ContentResolver.query (ContentResolver.java:766)
  at android.content.ContentResolver.query (ContentResolver.java:716)
  at androidx.core.content.ContentResolverCompat.query (ContentResolverCompat.java:81)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:63)
  at androidx.loader.content.CursorLoader.loadInBackground (CursorLoader.java:41)
  at androidx.loader.content.AsyncTaskLoader.onLoadInBackground (AsyncTaskLoader.java:307)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$532ebdd5 (AsyncTaskLoader.java:60)
  at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground$42af7916 (AsyncTaskLoader.java:48)
  at androidx.loader.content.ModernAsyncTask$2.call (ModernAsyncTask.java:141)
  at java.util.concurrent.FutureTask.run (FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:636)
  at java.lang.Thread.run (Thread.java:784)
java android 多线程 nullpointerexception decimalformat

评论

1赞 Andy Turner 9/27/2019
这不是由代码中的同步问题引起的。 受线程限制,因此无法让其他线程访问它。decimalFormat
0赞 Wirling 9/27/2019
@AndyTurner所以可能是什么问题,正如您在堆栈跟踪中看到的那样,NullPointer 不是由我的代码引起的,decimalFormat 和 bigDecimal 不是 null。
1赞 Andy Turner 9/27/2019
看起来像库中的错误。
0赞 Didier L 9/27/2019
无论如何,你是从解组中走出来的吗?因为 NPE 发生在 putLittleEndianInts 内部,这只有在数组参数为 时才有可能,该参数来自包装的 BigInteger.digits,即 。BigDecimalnulltransient
0赞 Wirling 9/28/2019
@DidierL我认为它确实如此,因为 BigDecimal 来自 Parcelable 类。

答: 暂无答案