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


130

나는 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)

1
좀 더 컨텍스트를 제공 할 수 있습니까? 엔터티를 병합, 업데이트 또는 삭제하고 있습니까? 이 단체는 어떤 협회를 가지고 있지 않습니까? 계단식 설정은 어떻습니까?
ordnungswidrig

1
스택 추적에서 HashMap을 반복하는 동안 예외가 발생 함을 알 수 있습니다. 다른 스레드가 맵을 수정하고 있지만 반복하는 스레드에서 예외가 발생합니다.
Chochos

답변:


263

이것은 동기화 문제가 아닙니다. 반복되는 기본 컬렉션이 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()작업을 지원한다고 가정 합니다.


1
아마도 최대 절전 모드가 반복 작업을 수행하는 것처럼 보이므로 합리적으로 올바르게 구현해야합니다. 지도를 수정하는 콜백이있을 수 있지만 그럴 가능성은 없습니다. 예측 불가능 성은 실제 동시성 문제를 가리 킵니다.
Tom Hawtin-tackline

이 예외는 스레딩 동시성과 관련이 없으며, 반복자의 백업 저장소가 수정되어 발생합니다. 다른 스레드에 의한 것이 반복자에게 중요하지 않은지 여부. IMHO 원인에 대한 잘못된 인상을주기 때문에 이름이 잘못 지정된 예외입니다.
Robin

그러나 예상 할 수없는 경우이 예외 조건이 발생하는 스레딩 문제가 발생했을 가능성이 높습니다. 예외 이름 때문에 더 혼란 스럽습니다.
Robin

이것은 정답보다 더 정확하고 더 나은 설명이지만, 정답은 좋은 해결책입니다. ConcurrentHashMap은 반복자 내부에서도 CME의 적용을받지 않습니다 (반복자는 여전히 단일 스레드 액세스를 위해 설계되었지만).
G__

지도에는 iterator () 메소드가 없으므로이 솔루션은 의미가 없습니다. Robin의 예는 예를 들어 목록에 적용 할 수 있습니다.
피터

72

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


정말 문제가 해결 되었습니까? 동일한 문제가 발생하지만 스레딩 문제를 배제 할 수 있습니다.
tobiasbayer

5
다른 해결책은 맵 사본을 작성하고 대신 해당 사본을 반복하는 것입니다. 또는 키 세트를 복사하고 반복하여 각 맵의 값을 원래 맵에서 가져옵니다.
Chochos

컬렉션을 반복하는 사람은 최대 절전 모드이므로 간단히 복사 할 수 없습니다.
tobiasbayer

1
즉석 구세주. 왜 이것이 그렇게 잘 작동했는지 살펴보면 더 이상 놀라지 않을 것입니다.
Valchris

1
동기화 문제가 아니라고 생각합니다. 동일한 객체를 반복하면서 동일한 수정을 수정하면 문제가됩니다.
Rais Alam

17

대부분의 클래스 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


단일 스레드의 경우 동시 수정이 허용되지 않는 이유가 궁금합니다. 단일 스레드가 일반 해시 맵에서 동시 수정을 수행 할 수 있으면 어떤 문제가 발생할 수 있습니까?
마스터 조

4

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

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

2

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

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

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


나는 그들이 Hashtable을 의미한다고 생각합니다. JDK 1.0의 일부로 제공되었습니다. Vector와 마찬가지로 스레드 안전하고 느리게 작성되었습니다. 둘 다 스레드가 아닌 안전한 대안 인 HashMap 및 ArrayList로 대체되었습니다. 사용한 것을 지불하십시오.
duffymo

0

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


0

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

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

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();
    }

0

목록에서 x 개의 마지막 항목을 제거하려고 할 때이 예외가 발생했습니다. myList.subList(lastIndex, myList.size()).clear();나를 위해 일한 유일한 솔루션이었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.