본문 바로가기

개발

for each 돌면서 list 삭제 할때 문제. ConcurrentModificationException

다음과 같은 역할을 하는 메소드가 있다.






실제 테스트를 실행했더니 

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

검색해보니 컬렉션에서 순회 중 데이터의 조작이 발생하는 경우 다음과 같은 에러 발생// ConcurrentModificationException 

 






이거랑 for each 랑 같은 역할을 함. 
향상된 for문은 컬렉션 객체를 내부적으로 iterator를 사용해서 반복시킴. 


일단 Array List 에 remove 하는 메소드를 보자. 





메소드를 보면 실제로 remove 하는 건 fastRemove가 하는 걸 알 수 있음.



이 fastRemove 안에는 modCount라는게 있는데 이게 왜 있는건지..?? 모르겟음. 


api 문서를 살펴 보니 modCount라는건 구조적으로 수정된 횟수를 나타낸다. 

구조적변경(예를들어 list의 사이즈 변경)은 진행중인 반복이 잘못된 결과를 유발할수 있다. 

 예상치 않게 iterator가 바뀌게 되면 next나 remove, previous, set, add 연산자에서 ConcurrentModificationException을 발생 시킨다고 함.  

 


이것과 관련 해서 iterator에서 fail-fast와 fail-safe 라는것도 연관되서 나왔는데 나온김에 정리. 

fail-fast 속성은 java.util 패키지에 들어가있고 다음 엘리먼트에 접근 하려고 할 때 엘리먼트가 변한것이 있는지 확인하는 것이다. 만약 수정 사항이 발견된다면 ConcurrentModificationException를 발생시킨다. 모든 Iterator의 구현체는 ConcurrentHashMap이나 CopyOnWriteArrayList 동시성 관련된 컬렉션을 제외 하고 처럼 fail-fast를 사용하는 방법으로 디자인 되어 있다.

fail-safe는 java.util.concurrent 패키지에 위치하도록 디자인되어 있다. Fail-fast Iterator는 ConcurrentModificationException 을 발생 시키고 fail-safe는 절대로 ConcurrentModificationException를 발생 시키지 않다. 


컬렌션을 순회하는 도중에 ConcurrentModificationException이 발생하는것을 피할려면 어떻게 해야 되는가?
  concurrent 컬렉션을 사용하면 ConcurrentModificationException이 발생하는것을 예방 할 수 있다. ex) ArrayList 대신 CopyOnWriteArrayList를 사용

또 여기서 concurrent라는 패키지가 중요하다고 생각해서 찾아봄. 
자바 1.5에 추가된 패키지...... 동시성 프로그래밍에 유용한 클래스를 모아둠. 


여기서 자바 8이 나오면서 또 한번 변화가 일어남. 

for - each문을 쓰는건 외부 반복인데 자바 8에 추가 된 stream 을 쓰면 내부 반복으로 처리됨. 

내부적으로 처리하면 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다. 

외부반복을 쓴다면 병렬성을 스스로 관리해야한다. 


stream 에서 내부적으로 반복을 숨겨주는 연락 리스트가 미리 정의 되어 있어야 한다.(filter나 map)


출처: http://starplatina.tistory.com/entry/자바-컬렉션-프레임워크-인터뷰-질문-40개 https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html