Java Concurrent Modification Exception
A typical unit test to detect the concurrent exception issue; I used Collections.synchronizedList (lock on the while list for both read and write) and CopyOnWriteArrayList (make a copy on write and no lock on read); The rule is similar for HashTable (lock the whole collection on both read and write), Collections.synchronizedMap (lock the whole collection on both read and write) and ConcurrentHashMap (Segments write, which is a bit different from CopyOnWriteArrayList)
public class Tests { @Test public void test_ConcurrentModificationException_Fail1() { final List<Integer> list = new ArrayList<>(); helper(list, SomeClass::doSth); } // this failure is fail-fast, which seems not necessary for me @Test public void test_ConcurrentModificationException_Fail2() { final List<Integer> list = Collections.synchronizedList(new ArrayList<>()); helper(list, SomeClass::doSth); } @Test public void test_ConcurrentModificationException_Fail3() { final List<Integer> list = new ArrayList<>(); helper(list, SomeClass::doSthSync); } @Test public void test_ConcurrentModificationException_Pass1() { final List<Integer> list = Collections.synchronizedList(new ArrayList<>()); helper(list, SomeClass::doSthSync); } @Test public void test_ConcurrentModificationException_Pass2() { final List<Integer> list = new CopyOnWriteArrayList<>(); helper(list, SomeClass::doSthSync); } @Test public void test_ConcurrentModificationException_Pass3() { final List<Integer> list = new CopyOnWriteArrayList<>(); helper(list, SomeClass::doSth); } // a typical helper to expose concurrent modification exception // Consumer is one of the possible functional interfaces; you should use the suitable functional interface here // the input data is also case by case private static void helper(final List<Integer> list, final Consumer<List<Integer>> consumer) { final int count = 1000; //first thread is changing the data in a quick fashion new Thread(() -> { try { for (int i = count; i >= 1; i--) { list.add(i); Thread.sleep(1); } } catch (InterruptedException e) { e.printStackTrace(); fail("Test failed due to sleep thread InterruptedException"); } catch (RuntimeException e) { e.printStackTrace(); fail("Test failed due to RuntimeException and could be related to TFS930700"); } }).start(); // Meanwhile the second thread is frequently calling the tested function which uses the same data try { for (int i = 0; i < count; i++) { consumer.accept(list); Thread.sleep(1); } } catch (final InterruptedException e) { e.printStackTrace(); fail("Test failed due to sleep thread InterruptedException"); } catch (final RuntimeException e) { e.printStackTrace(); fail("Test failed due to RuntimeException and could be related to TFS930700"); } // the main thread to wait for a little longer time until the above two threads completed try { Thread.sleep(count * 2); } catch (final InterruptedException e) { e.printStackTrace(); fail("Test failed due to sleep thread InterruptedException"); } Assert.assertEquals(count, list.size()); // just a minor verification } } public class SomeClass { // unsafe public static void doSth(final List<Integer> list) { for (final int k : list) { if (k < 0) { return; } } } // unsafe too public static void doSthSync(final List<Integer> list) { synchronized (list) { for (final int k : list) { if (k < 0) { return; } } } } }
page revision: 8, last edited: 30 Oct 2019 07:15