AtomicInteger와 같이 AtomicReference를 사용하면 ConcurrentModificationException(이하 CME)를 만날 수 있다. 


AtomicReference(Collection)으로 사용하고 있을 때 내부적으로 AtomicReference 내부의 volatile으로 저장된 Collection value은 set/get에 대해서는 atomic 한다. 하지만 volatile Collection value의 iterator는 atomic하지 않을 수 있다. 따라서 AtomicReference로 감싸고 있는 Collection을 사용할 때는 주의하면서 써야할 필요가 있다. 


Java 7 이전에는 Collections.sort() 메소드 호출시 문제가 있을 수 있었지만, 체크하는 로직이 없었다. CME가 발생되지 않았다.


Collections를 새롭게 구현한 Java8의 Collections.sort() 내에서 소팅하기 전에 count(정확히 AbstractList#modCount, https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html#modCount)를 저장하고 소팅하고 나서 저장된 count가 기존에 저장된 것과 비교해서 다르면 CME를 발생하도록 되어 있다. 좀 더 고급스럽게 말하면 CME를 빨리 발생시켜주는 fast-fail iterator를 위한 것이다. 


아래 코드는 Java 7를 포함, 그 이하버전에서는 CME가 발생하지 않았다. 


private AtomicReference<List<String>> myList = new AtomicReference<List<String>>();


public List<String> sort() {

List<String> result = new ArrayList<String>();


List<String> list = this.myList.get();


if (list == null) {

return null;

}

for (String string : list) {

result.add(string);

}


Collections.sort(list);


return result;

}



그러나 Java8에서 Collections.sort() 에서 위에서 설명한 modCount 덕분에 빨리 CME가 발생하게 되었다.


관련해서는 http://stackoverflow.com/questions/10658478/concurrent-modification-exceptions에서 비슷한 내용이 있다.



재미있는 것은 안전한 iterator를 구현한 ConcurrentMap 이다. weak consistent한 iterator를 보장한다.

그리고, set/get/iterator의 안전함이 ConcurrentMap의 thread-safe를 구현하고 있다.  관련 내용은 아래 링크에 자세히 설명되어 있다. 


http://books.google.co.kr/books?id=O6fJBAAAQBAJ&pg=PA28&lpg=PA28&dq=volatile+map+java+iterator+concurrentmodificationexception&source=bl&ots=YhhEoYJIeG&sig=BfmN06mvGZ6EBYaT24JRsro7xV4&hl=ko&sa=X&ei=h_BjVNW1D-bOmwW904HwAQ&ved=0CE4Q6AEwBQ#v=onepage&q=volatile%20map%20java%20iterator%20concurrentmodificationexception&f=false



Posted by 김용환 '김용환'

댓글을 달아 주세요