JavaRush /Java Blog /Random-KO /두 반복자의 이야기: Java의 경쟁적 수정 전략

두 반복자의 이야기: Java의 경쟁적 수정 전략

Random-KO 그룹에 게시되었습니다
이 노트의 작성자는 크라쿠프(폴란드)의 소프트웨어 개발자인 Grzegorz Mirek입니다. 그는 약 6년 전 대학 재학 중에 Java 개발을 시작했으며 그 이후로 이 분야에서 끊임없이 기술을 연마해 왔습니다. 그는 특히 자신의 블로그 에 주로 글을 쓰고 있는 JVM 성능 및 최적화에 관심이 있습니다 .
두 반복자 이야기: Java의 경쟁적 수정 전략 - 1
가장 인기 있는 Java 면접 질문 중 일부는 다음과 같습니다. 빠른 실패와 안전한 반복자의 차이점은 무엇입니까? 이에 대한 가장 간단한 대답은 다음과 같습니다. 반복 중에 컬렉션이 변경되면 빠른 실패 반복자는 ConcurrentModificationException을 발생시키지만 실패 방지 반복자는 그렇지 않습니다. 꽤 의미 있는 것처럼 들리지만 면접관이 안전 장치를 의미하는 것이 무엇인지는 여전히 불분명합니다. Java 언어 사양에서는 반복자와 관련하여 이 용어를 정의하지 않습니다. 그러나 네 가지 경쟁적 수정 전략이 있습니다.

경쟁적 수정

먼저, 경쟁적(또는 병렬) 수정이 무엇인지 정의해 보겠습니다. 컬렉션이 있고 반복자가 활성화되면 이 반복자에서 발생하지 않는 일부 변경 사항이 발생한다고 가정해 보겠습니다. 이 경우 경쟁 수정을 얻습니다. 간단한 예를 들어 보겠습니다. 여러 스레드가 있다고 가정해 보겠습니다. 첫 번째 스레드는 반복되고 두 번째 스레드는 동일한 컬렉션에서 요소를 삽입하거나 제거합니다. 그러나 단일 스레드 환경에서 실행하는 경우 ConcurrentModificationException이 발생할 수 있습니다.
List<String> cities = new ArrayList<>();
cities.add(Warsaw);
cities.add(Prague);
cities.add(Budapest);

Iterator<String> cityIterator = cities.iterator();
cityIterator.next();
cities.remove(1);
cityIterator.next(); // генерирует ConcurrentModificationException

빠른 실패

위의 코드 조각은 빠른 실패 반복자 의 예입니다 . 보시다시피, iterator에서 두 번째 요소를 검색하려고 할 때 ConcurrentModificationException이 발생했습니다 . 반복자는 컬렉션이 생성된 이후 수정되었음을 어떻게 알 수 있나요? 예를 들어 컬렉션에는 lastModified 와 같은 날짜/시간 스탬프가 있을 수 있습니다 . 반복자를 생성할 때 이 필드를 복사하여 반복자 객체에 저장해야 합니다. 그런 다음 next() 메서드를 호출할 때마다 컬렉션의 lastModified 값 과 반복자의 복사본을 간단히 비교하게 됩니다 . 예를 들어 ArrayList 클래스 구현에는 매우 유사한 접근 방식이 사용됩니다 . 목록이 수정된 횟수를 저장하는 인스턴스 변수 modCount 가 있습니다 .
final void checkForComodification() {
   if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
}
빠른 실패 반복자는 동종 최고 기준으로 작동한다는 점에 유의하는 것이 중요합니다 . 즉 , 동시 수정 시 ConcurrentModificationException이 발생한다는 보장이 없다는 의미입니다. 따라서 이에 의존해서는 안 됩니다. 오히려 오류를 감지하는 데 사용해야 합니다. 대부분의 비동시 컬렉션은 빠른 실패 반복자를 제공합니다.

약한 일관성

java.util.concurrent 패키지 (예: ConcurrentHashMap 및 대부분의 Queue ) 의 대부분 동시 컬렉션은 약하게 일관된 반복자를 제공합니다. 이 용어의 의미는 문서 에 매우 잘 설명되어 있습니다 .
  • 다른 작업과 동시에 처리할 수 있습니다.
  • ConcurrentModificationException을 발생시키지 않습니다.
  • 반복자가 생성된 시점에 기존 요소를 정확히 한 번 순회하는 것이 보장되며 후속 수정 사항을 반영할 수 있습니다(필수는 아님).

스냅 사진

이 전략을 사용하면 반복자는 생성 당시 컬렉션의 상태와 연결됩니다. 이는 컬렉션의 스냅샷입니다. 원본 컬렉션을 변경하면 기본 데이터 구조의 새 버전이 생성됩니다. 이렇게 하면 스냅샷이 변경되지 않은 상태로 유지되므로 반복자가 생성된 후 발생한 컬렉션에 대한 변경 사항이 반영되지 않습니다. 이것은 오래된 COW(기록 중 복사) 기술입니다 . 이는 동시 수정 문제를 완전히 해결하므로 이 접근 방식에서는 ConcurrentModificationException 이 생성되지 않습니다. 또한 반복자는 요소를 변경하는 작업을 지원하지 않습니다. 기록 중 복사 컬렉션은 사용하기에 비용이 너무 많이 드는 경향이 있지만, 반복자 순회보다 변경 빈도가 훨씬 낮은 경우에는 사용하는 것이 좋습니다. 예를 들면 CopyOnWriteArrayListCopyOnWriteArraySet 클래스가 있습니다 .

정의되지 않은 동작

VectorHashtable 과 같은 레거시 컬렉션 유형에서 정의되지 않은 동작이 발생할 수 있습니다 . 둘 다 표준적인 빠른 실패 반복자를 가지고 있지만 추가적으로 Enumeration 인터페이스 의 구현을 사용할 수 있으며 동시 수정 시 어떻게 작동할지 모릅니다. 일부 요소가 반복되거나 누락되거나 이상한 예외가 발생할 수도 있습니다. 그들과 놀지 않는 것이 좋습니다!
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION