Collection의 Fail-fast
참조 : Fail-fast Iterator에 대한 멀티쓰레드 무결성 해결방법
Fail-fast란? Fail-fast vs. complete validation
Collection Iteration에서의 Fail-fast
맨 위에 있는 참조 링크를 참조하시면 알 수 있지만 Iterator는 Enumeration과 달리 snapshot을 사용하지 않고 해당 콜렉션 자체에 대한 링크를 사용하여 traversal 하기 때문에 콜렉션이 변하게 되면 자신이 현재 참조 하고 있는 콜렉션에 대한 view가 correctness가 보장되지 않기 때문에 fail-fast하게 됩니다.
Iterator가 Fail-fast 하는 원리
modification number라는 것을 사용하여 view하고 있는 콜렉션에 대한 수정(새로운 요소 추가 or 기존 요소 변경 삭제)이 일어나면 modification number가 변합니다.
매번 다음 요소를 view하기 전에 이 넘버를 체크 하고 처음에 시작할 때의 number와 다르면 concurrrentModificationExceptioin이 발생합니다.
참조 : ConcurrentModificationException 살펴보기
iterator의 Fail-fast 방지하기
Fail-fast자체를 원천 봉쇄 하는 방법은 때에 따라 적절한 행동이 아닐 수도 있습니다. 하지만 멀티 쓰레드 환경에서 그다지 원하는 상황은 아닐 것 입니다. 하나의 쓰레드는 리스트를 쭉 훑어 보고 싶고 하나의 쓰레드는 리스트에서 하나를 제거 하고 싶습니다. 둘 이 따로따로 동작하면 아무런 문제가 생기지 않지만 하필이면!!! 리스트를 훝어보던 쓰레드가 잠깐 멈추고 그 사이 다른 쓰레드가 리스트에서 하나를 제거하면 Fail-fast가 발생하고 ConcurrentModificationException이 발생합니다. 이걸 방지하는 방법으로 제가 알고 있는 방법은 두 개.
1. 루프를 동기화 시키기.
2. Snapshot 이용하기.
3. 동기화 된 콜렉션 사용하기.
동기화 된 콜렉션 사용하기.
이 방법에 선을 그어 놓은 것은 Fail-fast를 방지 하지 못하기 때문입니다. 동기화 된 콜렉션으로는 이 전글 Collection와 Thread 1, Collection와 Thread 2에서 확인할 수 있습니다. 이 콜렉션이 사용하는 iterator 메소드를 살펴보면 다음과 같이 주석이 달려있습니다.
루프를 동기화 시키기.
리스트를 view하는 iteration 과정을 콜렉션의 lock을 사용하여 동기화 시킵니다. 그럼 위의 경우에 하나의 쓰레드가 해당 이터레이션을 하고 있을 때 다른 쓰레드는 리스트에 대한 접근이 막히게 됩니다.
단점은 이터레이션이 길어지면 락이 걸리는 기간이 길어지기 때문에 동기화의 여러 단점들(데드락, 컨텍스트 스위칭 비용)이 발생할 수 있는 여지가 많아집니다.
Snapshot 이용하기.
해당 콜렉션을 복사해서 보는 방법입니다. 복사 비용이 발생한다는 단점이 있지만 확실하게 Fail-fast가 방지 됩니다. 그리고 위에서 살펴본 루프를 동기화 시킬 때 발생하는 동기화 문제를 방지 할 수 있습니다. Snapshot을 사용하는 방법은 다음에 더 살펴보도록 하겠습니다.