简体   繁体   中英

Prevent ConcurrentModificationException when creating ArrayList from LinkedHashSet

We are performing a load testing / benchmark of a banking application. We are getting ConcurrentModificationException as one of the errors when running with around 100 virtual users. Below is the stacktrace:

java.util.ConcurrentModificationException
    at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:373)
    at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:384)
    at java.util.AbstractCollection.toArray(AbstractCollection.java:124)
    at java.util.ArrayList.<init>(ArrayList.java:131)
    at my.package.AuthorizationHelper.getAuthModuleList

Below is the section in getAuthModuleList() which is causing the exception:

private static final LinkedHashSet MODULE_SET = new LinkedHashSet();

public static List getAuthModuleList(..)
{
   MODULE_SET.clear();
   ....

   MODULE_SET.add(getAllrequiredModules());

   List userLevelModules = getAllUserLevelModules();

   if (userLevelModules != null) {
      MODULE_SET.addAll(userLevelModules);
   }

   userLevelModules = new ArrayList(MODULE_SET); //Exception here
   return userLevelModules;
}

Modules need to be executed in order with required ones first, hence using LinkedHashSet .

Following is my understanding of the cause for the CME:

  1. Transaction for user A calls this method.
  2. At the same time, user B has also called this method.
  3. By the time user A reaches the exception line, concurrent access by user B has already mutated MODULE_SET .
  4. Because of the above, the implementation of the ArrayList throws the exception.

How the code should be modified to prevent the above without breaking functionality as per my understanding:

Change this line:

userLevelModules = new ArrayList(MODULE_SET);

To this snippet:

LinkedHashSet moduleSetCopy = new LinkedHashSet(MODULE_SET);
// userLevelModules = new ArrayList(MODULE_SET);
userLevelModules = new ArrayList(moduleSetCopy);

So my questions are,

  • Is my analysis correct?
  • Should I make a synchronized copy using one of the Collections methods or is a normal copy enough?

Note that the application uses Oracle JRockit(R) (build R28.2.5-20-152429-1.6.0_37-20120927-1915-windows-x86_64, compiled mode) and not the standard Sun JDK. We need to simulate production level environment hence not using the latter.

UPDATE : Not sure if relevant to answering, but MODULE_SET is being cleared at the start of the method.

Ouch, hopefully it is not my bank that is going to use this application ;-)

Anyway, you are modifying a shared resource (MODULE_SET) without proper synchronization or locks, and yor analysis is correct. The solution, however, is to not use the shared resource at all if the list of modules is user or request dependent, or initialize it once if it is a true singleton.

For the first alternative, the code could look like this:

public static List getAuthModuleList(..)
{
    LinkedHashSet MODULE_SET = new LinkedHashSet();

    MODULE_SET.add(getAllrequiredModules());
    List userLevelModules = getAllUserLevelModules();

    if (userLevelModules != null) {
       MODULE_SET.addAll(userLevelModules);
    }
    userLevelModules = new ArrayList(MODULE_SET); //Exception here
    return userLevelModules;
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM