[java] ConcurrentModificationException이 발생하는 이유 및 디버깅 방법

나는 Collection( HashMapJPA에 의해 간접적으로 사용되는) 그렇게 사용하고 있지만 분명히 코드는 무작위로을 던진다 ConcurrentModificationException. 무엇이 원인이며이 문제를 어떻게 해결합니까? 아마도 동기화를 사용함으로써?

전체 스택 추적은 다음과 같습니다.

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
        at java.util.HashMap$ValueIterator.next(Unknown Source)
        at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)



답변

이것은 동기화 문제가 아닙니다. 반복되는 기본 컬렉션이 Iterator 자체 이외의 다른 항목으로 수정 된 경우에 발생합니다.

Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}

이는 발생합니다 ConcurrentModificationException(가) 때 it.hasNext()두 번째라고합니다.

올바른 접근 방식은

   Iterator it = map.entrySet().iterator();
   while (it.hasNext())
   {
      Entry item = it.next();
      it.remove();
   }

이 반복자가 remove()작업을 지원한다고 가정 합니다.


답변

ConcurrentHashMap평원 대신에 사용해보십시오HashMap


답변

대부분의 클래스 Collection에서는를 Collection사용하여 반복 하는 동안 시간을 수정 하는 Iterator것은 허용되지 않습니다Collection . Java 라이브러리 Collection는 “동시 수정”을 반복 하는 동안 수정을 시도합니다 . 불행히도 가능한 유일한 원인은 여러 스레드가 동시에 수정하는 것이지만 그렇지는 않습니다. 하나의 스레드 만 Collection사용하여 Collection.iterator()(또는 Enhanced for루프를 사용하여) 반복자를 작성하고 , 반복을 시작하거나 ( Iterator.next()Enhanced for루프 의 본문을 사용 하거나 이와 동등하게 입력 )을 수정 한 Collection다음 반복을 계속할 수 있습니다.

도움 프로그래머, 일부 사람들의 구현 Collection클래스 를 시도 잘못된 동시 변경을 감지하고, 던져 ConcurrentModificationException그들이 그것을 감지합니다. 그러나, 모든 동시 수정의 검출을 보장하는 것은 일반적으로 불가능하고 실용적이지 않다. 따라서 잘못 사용하여 Collection항상 발생 하는 것은 아닙니다 ConcurrentModificationException.

의 문서는 ConcurrentModificationException말합니다 :

이 예외는 그러한 수정이 허용되지 않을 때 객체의 동시 수정을 감지 한 메소드에 의해 발생할 수 있습니다 …

이 예외가 항상 다른 스레드에 의해 객체가 수정되었음을 나타내는 것은 아닙니다. 단일 스레드가 객체의 계약을 위반하는 일련의 메소드 호출을 발행하면 객체에서이 예외가 발생할 수 있습니다.

일반적으로 말해서 비동기식 동시 수정이있을 경우 확실한 보장을 할 수 없으므로 페일-패스트 동작을 보장 할 수 없습니다. 빠른 작업 ConcurrentModificationException은 최선의 노력으로 이루어집니다.

참고

  • 예외 던져 질 수 있으며 던져 질 필요 없다
  • 다른 스레드가 필요하지 않습니다
  • 예외를 던지는 것은 보장 할 수 없습니다
  • 예외를 던지는 것은 최선의 노력으로 이루어집니다
  • 동시 수정이 감지 될 때가 아니라 예외가 발생 하면 예외 발생합니다.

의 문서 HashSet, HashMap, TreeSetArrayList수업이 말한다 :

[이 클래스에서 직접 또는 간접적으로] 리턴 된 반복자는 실패가 빠릅니다. 반복자의 고유 한 remove 메소드를 제외하고 어떤 식 으로든 반복자가 작성된 후 [컬렉션]이 수정되면 Iteratorthrow는 ConcurrentModificationException. 따라서 동시 수정에 직면하여 반복자는 미래에 결정되지 않은 시간에 임의의 비 결정적 동작을 위험에 빠뜨리기보다는 신속하고 깨끗하게 실패합니다.

일반적으로 말하자면, 비 동기화 된 동시 수정이있을 경우 어떠한 엄격한 보장도 불가능하므로 반복자의 페일-패스트 동작을 보장 할 수 없습니다. 빠른 속도의 이터레이터 ConcurrentModificationException는 최선의 노력으로 던집니다 . 따라서이 예외에 따라 프로그램이 정확 하지 않은 경우 작성하는 것이 잘못 될 수 있습니다. 이터레이터의 빠른 동작은 버그를 탐지하는 데만 사용해야합니다 .

이 동작은 “보장 할 수 없으며”최선의 노력으로 만 이루어집니다.

Map인터페이스 의 여러 방법에 대한 문서는 다음과 같이 말합니다.

비 동시 구현은이 메소드를 대체해야하며 최선의 노력으로 ConcurrentModificationException맵핑 함수가 계산 중에이 맵을 수정하는 것이 감지되면 if를 처리하십시오. 동시 구현은이 메소드를 대체해야하며 최선의 노력으로 IllegalStateException맵핑 함수가 계산 중에이 맵을 수정하고 결과적으로 계산이 완료되지 않는 것으로 감지되면 if를 처리하십시오.

검색에는 “최선의 노력” ConcurrentModificationException만 필요하며, 비 동시 (스레드에 안전하지 않은) 클래스에만 명시 적으로 제안됩니다.

디버깅 ConcurrentModificationException

따라서로 인해 스택 추적 ConcurrentModificationException이 표시되면 원인이 안전하지 않은 멀티 스레드 액세스라고 가정 할 수 없습니다 Collection. 당신은해야한다 스택 추적을 검토 중 어떤 클래스를 결정하기 위해 Collection예외 던졌다 (클래스의 방법은 직접 또는 간접적으로 발생해야합니다), 그리고있는 Collection객체입니다. 그런 다음 해당 오브젝트를 수정할 수있는 위치에서 검사해야합니다.

  • 가장 일반적인 원인은에 Collection대한 향상된 for루프 내의 수정입니다 Collection. 당신이 보이지 않는해서 Iterator의미하지 않는다 소스 코드에서 객체를 더 없다 Iterator거기! 다행히도 잘못된 for루프 의 문 중 하나 가 일반적으로 스택 추적에 있으므로 오류를 쉽게 추적 할 수 있습니다.
  • 까다로운 경우는 코드가 Collection객체에 대한 참조를 전달할 때 입니다. 참고 불가능한 (제조와 같은 모음 뷰 Collections.unmodifiableList()정도)의 수정 컬렉션에 대한 참조를 유지하기 에 “불가능한”컬렉션 예외를 발생시킬 수 이상 반복 (변형 예는 다른 곳에서 수행되었다). 하위 목록 , 항목 세트키 세트 와 같은 의 다른 보기 도 원본 (수정 가능)에 대한 참조를 유지 합니다. 이것은 심지어 스레드 안전에 대한 문제가 될 수 있습니다 와 같은, ; 스레드 안전 (동시) 컬렉션이 예외를 throw 할 수 없다고 가정하지 마십시오.CollectionMapMapCollectionCollectionCopyOnWriteList
  • 경우에 따라 어떤 작업을 수정하여 Collection예기치 않은 결과가 발생할 수 있습니다. 예를 들어 LinkedHashMap.get()컬렉션을 수정합니다 .
  • 가장 어려운 경우는 예외 여러 스레드에 의한 동시 수정으로 인한 경우 입니다 .

동시 수정 오류를 방지하기위한 프로그래밍

가능하면 Collection객체에 대한 모든 참조를 제한 하므로 동시 수정을 쉽게 방지 할 수 있습니다. 만들기 기능 객체 또는 로컬 변수 및 참조를 반환하지 않는 방법 또는 그 반복자. 그러면 수정 가능한 모든 장소 를 검사 하는 것이 훨씬 쉽습니다 . (가) 경우 여러 스레드에 의해 사용되는, 스레드가 액세스 할 수 있도록 다음 실용적인 적절한 개시 또는 종료 이벤트들이 발생하고 잠금 만.CollectionprivateCollectionCollectionCollectionCollection


답변

Java 8에서는 람다 식을 사용할 수 있습니다.

map.keySet().removeIf(key -> key condition);


답변

Java 동기화 문제보다는 데이터베이스 잠금 문제와 비슷합니다.

모든 영구 클래스에 버전을 추가하면 버전이 정렬되는지 알 수 없지만 Hibernate가 테이블의 행에 독점적으로 액세스 할 수있는 방법 중 하나입니다.

격리 수준이 더 높아야 할 수 있습니다. “더러운 읽기”를 허용하면 직렬화 가능해야 할 수도 있습니다.


답변

수행하려는 작업에 따라 CopyOnWriteArrayList 또는 CopyOnWriteArraySet을 시도하십시오.


답변

나처럼지도를 반복하는 동안지도에서 일부 항목을 제거하려는 경우 선택한 답변을 수정하기 전에 컨텍스트에 직접 적용 할 수 없습니다.

초보자가 시간을 절약 할 수 있도록 여기에 실제 예제를 제공합니다.

HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
    Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
    //it.remove() will delete the item from the map
    if((Integer)item.getValue()<threshold){
        it.remove();
    }