如何忽略ConcurrentModificationException

huangapple go评论179阅读模式
英文:

How to ignore ConcurrentModificationException

问题

我有一个Java 11、JavaFX、Spring JPA / boot、MySQL的应用程序。当这个应用程序启动时,会拉取大量数据来计算几个关键绩效指标(KPI),就像在仪表板中一样。也许将来我会把这些计算移到数据库中,但目前还是在应用程序中进行。每个KPI都是在一个专用的线程中计算的。一旦KPI可用,它就会显示在UI的一个区域中。用户可能会并且很可能会开始使用应用程序的其他功能。很可能这些功能操作的是与KPI计算相同的对象。因此,当发生这种情况时,我总是会得到ConcurrentModificationException(并发修改异常)。然而,当其他功能加载数据时发生了这种情况,而这些数据不是用户修改或保存的数据。因此我感到很惊讶。我在调试器中设置了断点,这样无论何时访问带有相关对象的ArrayList时,它都应该停止。除了我的KPI计算在访问它之外,从未发生过。因此,在尝试理解这一点时,事实证明,由于乐观锁定,Spring会更改对象域的版本号。所以,实际上,在计算KPI时,我不关心已更改的版本号。然而,麻烦的是KPI计算循环会抛出异常,因此我在我的KPI计算中会漏掉这个相应的对象。有没有办法“忽略”这个异常呢?换句话说,我怎样才能确保在下面的示例中仍然统计所有任务的工作量?

...  // 先前的代码

try {
    ArrayList taskList = taskService.getAllTasks();
    for(Task task: taskList) {                     // 这一行会抛出异常
       totalEffortHours += task.getEffort();
    }
} catch(ConcurrentModificationException cme) {
}

...  // 后续的代码

提前谢谢。

英文:

I have a Java 11, JavaFX, Spring JPA / boot, MySQL application. When this application starts, it pulls larger amounts of data to calculate several Key Performance Indicators [KPI] like in a dashboard. One day I might move this calculations to the database - but for the time being it is with the application. Each of these KPIs is calculated in a dedicated thread. Once the KPI is available, it is shown in the UI in an area of the UI. The user may and will likely start to use other functionality of the application. It is very likely, that this functionality operates on the same objects as the KPI calculation. Hence, when that happens, I always get a ConcurrentModificationException. However, it happened when the other functionality would load data - it is not the user modifying or saving data. Hence I was surprised. I added a breakpoint to the debugger, such that whenever something is accessing the ArrayList with the related Objects it should stop. That never happened, other then my KPI calculation was accessing it. So when trying to understand this, it turns out, that because of optimistic locking the version number of the object domains gets changed by Spring. So, actually I don't care about the changed version number while calculating the KPIs. However, the trouble is that the KPI calculation loop throws the Exception and hence I miss this according object in my KPI calculation. Is there a way to "ignore" the Exception? In other words, how would I make sure that in below example still all tasks efforts are counted?

...  // previous code

try {
    ArrayList taskList = taskService.getAllTasks();
    for(Task task: taskList) {                     // This line throws the Exception
       totalEffortHours += task.getEffort();
    }
} catch(ConcurrentModificationException cme) {
}

...  // following code

Thank you in advance.

答案1

得分: 1

> 有没有一种方法可以“忽略”异常?换句话说,在下面的示例中,我怎样才能确保仍然计算所有任务的工作量?

没有这种方法。

  • 如果您忽略了异常,那么就不会计算所有任务的工作量。
  • 的确... 如果异常被抛出,那么您就无法计算所有任务的工作量!

您需要弄清楚为什么会抛出异常,并更改代码,以确保不会发生异常。

根据您提供的代码片段,导致异常的并发修改有两个可能的原因:

  1. 在计算过程中,可能有其他线程(有时)更新了 taskService.getAllTasks() 调用返回的列表。

  2. task.getEffort() 调用以某种方式更改了列表。

第二个原因仅仅是理论上的。 (我无法想象有人会编写会这样做的代码!)这让我们只剩下一个合理的解释。另一个线程正在修改列表。

(这样的问题可能对时间非常敏感。我对它只偶尔发生,并且在您尝试调试代码时不会发生一点都不感到惊讶。)

如何解决(假定的)并发更新问题?

一种方法是修改代码,使任务服务的任务列表成为并发集合类型;即不会抛出并发修改异常的类型。可能的选择包括 CopyOnWriteArrayListConcurrentDeque 或其中一种并发集合或映射类。

另一种方法是使用互斥来防止在迭代列表以累积工作量值时对任务列表进行任何更新。

(请注意,仅复制非并发列表不是解决方案...因为在您复制时仍可能会发生并发修改异常!)

英文:

> Is there a way to "ignore" the Exception? In other words, how would I make sure that in below example still all tasks efforts are counted?

No there isn't.

  • If you ignore the exception then you won't count them all.
  • Indeed ... if the exception is thrown then you won't count them all!

You need to figure out why the exception is being thrown and change the code so that that doesn't happen.

Based on the code snippets you have provided, there are two possible causes for the concurrent modification that leads to the exception:

  1. Some other thread is (sometimes) updating the list returned by the taskService.getAllTasks() call while you are counting.

  2. The task.getEffort() call somehow changes the list.

The second cause is theoretical only. (I can't imagine someone writing code that did that!) Which leaves us with just one plausible explanation. Another thread is modifying the list.

(A problem like this is likely to be timing sensitive. I am not at all surprised that it only occurs occasionally, and that it doesn't happen when you attempt to debug the code.)

How to fix this (assumed) concurrent update problem?

One approach is to modify the code so that the task service's task list is a concurrent collection type; i.e. one that doesn't throw CCMEs. Possibilities include CopyOnWriteArrayList, ConcurrentDeque or one of the concurrent set or map classes.

The other approach is to use mutual exclusion to prevent any updates to the task list while you are iterating the list to accumulate the effort values.

(Note that just copying a non-concurrent list isn't a solution ... because the CCME will just happen while you are making the copy!)

huangapple
  • 本文由 发表于 2020年9月25日 21:57:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64065583.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定