Whiteship's Note


Collection의 Fail-fast

Java : 2007.05.01 12:42


참조 : 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을 사용하는 방법은 다음에 더 살펴보도록 하겠습니다.

'Java' 카테고리의 다른 글

ThreadLocal  (0) 2007.08.10
JMX  (0) 2007.06.06
Factory Method vs Constructor  (0) 2007.05.18
효율적인 문자열 연결 방법  (2) 2007.05.18
Collection과 Thread 3  (4) 2007.05.02
Collection의 Fail-fast  (0) 2007.05.01
숨어있는 Iterator 찾기  (0) 2007.05.01
Reflections on Java Reflection  (2) 2007.04.25
Collection과 Thread 2  (0) 2007.04.24
Collection과 Thread 1  (0) 2007.04.24
Thread와 Collection 관련 링크 모음  (0) 2007.04.19
top


Enumeration & Iterator

Java : 2006.11.07 19:48


Collection은 지난 번에 살짝 살펴 본 것 같네요. 이번에는 콜렉션 안에 들어있는 요스들을 하나하나 차례대로 페이지 넘기듯이 볼 수 있듯 해주는 Collection View라는 것에 대한 아티클을 읽어 봤습니다.

콜렉션 뷰에는 Enumeration 과 Iterator 그리고 ListIterator가 있는데.. 이중에 Iterator와 ListIterator는 비슷하기 때문에 Enumeration과 Interator의 차이에 대해 알아보겠습니다.

먼저 스냅샷에서 차이가 나는데요. 스냅샷은 여러 콜렉션의 요소들이 영화라고 했을 때 그 영화의 한 순간을 스냅샷이라고 하듯이 콜렉션의 어떤 요소 하나를 스냅샷이라고 하나보다.. 라고 생각을 했었는데요. 그런 순간을 유지 하기 위해서 그 한 순간의 상태를 따로 생성하는 경우와 그렇치 않은 경우가 있나봅니다. Enumeraion의 경우 스냅샷에 대한 보장을 한다는 말이 있는데요. 즉 그 순간 따로 객체를 만드는 것인가 봅니다. 하지만 Iterator는 그렇치 않다고 합니다.

콜렉션 뷰 객체인 Iterator와 ListIterator 객체가 fail-fast 방식인 이유는 스냅샷에 대한 보장을 포함하고있지 않기 때문이다. 즉, 콜렉션의 뷰인 Iterator 객체를 얻었을 때 그 순간의 상태를 따로 생성하지 않기 때문에,Iterator 객체에서 순차적으로 하부 콜렉션 객체를 접근할 때, 콜렉션 객체의 변경에 영향을 받게 된다.

그결과 스냅샷을 찍을 때 따로 객체를 생성하지 않고 원래 콜렉션 객체를 참조 하고 있는 Iterator를 사용하여 콜렉션의 요소들을 보고 있을 때 삭제 or 추가 가 발생할 때 데이타 무결성을 지키기 위해서 런타임 에러를 발생시킵니다. 이런것을 Fail-fast 방식이라고 합니다. 하지만 Enumeration은 그렇치 않겠죠. 그렇다면 'Iterator를 쓰면서 삭제 or 추가를 할 수는 없을까?' 라는 생각이 드는데요. 사실 제일 먼저 든 생각은 'List의 경우에 왜 Iterator를 사용하는 가?' 였습니다. List는 get(int) 메소드를 사용해서 요소들에 바로 접근을 할 수 있기 때문에 Iterator가 꼭 필요한가 라는 생각을 했었는데요. 이건 List의 경우일 때이고 다른 Collection 인터페이스에는 get()이라는 메소드가 없었습니다. 그리고 iterator() 메소드는 있었지요. 그것을 보고 아.. 모든 콜렉션 들은 iterator() 만 알면 모든 요소들에 접근 할 수 있도록 하는 것인가.. 라는 자문자답을 해보게 되었습니다. 다시 원래 질문으로 돌아가서 'Iterator를 사용하면서는 삭제 or 추가를 할 수 없을까?' 라는 바보 같은 질문입니다. 당연히 쓸수가 없지요. 런타임 에러가 발생한다고 했으니깐요. 하지만 스냅샷을 만들어 주면 어떨까요? 그런 방법이 아티클에서 소개되고 있었습니다.

public Iterator snapshotIterator(Collection collection) {
return new ArrayList(collection).iterator();
}

그리고 동기화에 대한 이야기 부분에서 '콜렉션 프레임워크는 동기화를 보장하고 있지 않다.' 라고 합니다. 하지만 Vector의 경우에는 예외라고 생각이 되네요. Vector 클래스에 보면 synchronized 키워드가 많이 있었습니다. 하지만 대부분의 콜렉션의 synchronized 키워드를 찾아 볼 수 없었습니다. 이 것은 동기화를 보장하고 있지 않다는 듯입니다. 즉 동시에 쓰기와 읽기가 가능해 진다는 것인데 이럴 때 발생할 수 있는 문제를 해결하려면 역시 동기화 시키는 방법이 제일 좋겠지요. 그러한 해결책도 역시 아티클에서 소개 되고 있습니다.

Collection c = Collections.synchronizedCollection(myCollection);

끝으로 무엇이 더 좋다고 할 수는 없을 듯합니다. 그때 그때 상황에 따라 다른 듯합니다. 각각의 상황에 적절한 방법을 찾으려면 역시 이것도 알고 저것도 알고 있어야 한다는거~ 따라서 열공!

ps : 분홍색 부분은 원문을 참조한 것입니다.

'Java' 카테고리의 다른 글

Java 오픈소스 되다.(GPL 산하의)  (1) 2006.11.13
Null은 객체인가 아닌가? 2  (3) 2006.11.12
Null은 객체인가 아닌가?  (13) 2006.11.12
Reference Object 활용  (2) 2006.11.09
XML을 EXEL 파일로 바꾸기  (0) 2006.11.08
Enumeration & Iterator  (2) 2006.11.07
Auto (un)boxing은 -128~127  (4) 2006.11.07
탬플릿 클레스 만들기  (3) 2006.11.05
local inner class의 예  (0) 2006.11.05
객체지향의 구멍 static  (0) 2006.11.04
i++와 i=i+1 속도 비교  (0) 2006.11.02
top


Blocks and Iterators

RUBY/Manual : 2006.11.03 11:45


이번 세션에서는 간단하게 Ruby의 장점중에 하나를 설명하겠습니다. 코드 블럭을 살펴보려고 합니다. 코드 블럭은 callback을 구현하거나(Java의 anonymous inner class보다 간단하게), 코드 덩어리(C의 funtion pointer보다 더 유연한)를 넘겨주거나, 이터레이터를 구현할 때 사용할 수 있습니다.

코드 블럭은 ( )사이 또는 do 와 end 사이의 코드 덩어리 입니다.
{ puts "Hello" }       # this is a block

do                     #
 club.enroll(person)  # and so is this
 person.socialize     #
end                    #

코드 블럭을 만들어 두고 yield 를 사용하여 여러번 호출 할 수 있습니다.

def callBlock
 yield
 yield
end
callBlock { puts "In the block" }

callBlock 이라는 메소드 안에는 yield 를 두 번 사용했습니다. 그리고 오른쪽에 코드 블럭이 있군요. 그럼 이 코드 블럭을 두 번 호출 하게 될 테니 화면에 in the block 가 두번 찍힐 것입니다.

In the block

In the block

You can provide parameters to the call to
yield: these will be passed to the block. Within the block, you
list the names of the arguments to receive these parameters between
vertical bars (``|'').
def callBlock yield , end callBlock { |, | ... }

코드 블럭은 Ruby에서 이터레이터를 구현할 때 사용됐습니다.
a = %w( ant bee cat dog elk )    # create an array
a.each { |animal| puts animal }  # iterate over the contents
이 것의 결과는 다음과 같습니다.
ant
bee
cat
dog
elk

each라는 메소드가 어떻게 구현되어 있는지 보겠습니다.

# within class Array...
def each
 for each element
   yield(element)
 end
end
You could then iterate over an array's elements by calling itseach method and supplying a block. This block would be called foreach element in turn.
[ 'cat', 'dog', 'horse' ].each do |animal|
 print animal, " -- "
end
produces:
cat -- dog -- horse --

Java와 C에서 사용하는 여러 loof를 Ruby에서는 단순한 메소드 호출을 사용하여 할 수 있습니다.

5.times {  print "*" }
3.upto(6) {|i|  print i }
('a'..'e').each {|char| print char }

produces:

*****3456abcde

*을 출력하는 코드 블럭을 다섯번 호출하고, 3~6까지 출력하고, a~e까지 출력했습니다.


'RUBY > Manual' 카테고리의 다른 글

Reading and 'Riting  (0) 2006.11.04
Blocks and Iterators  (0) 2006.11.03
Regular Expressions  (0) 2006.10.05
Control Structures  (0) 2006.10.04
Arrays and Hashes  (0) 2006.10.02
Some Basic Ruby  (0) 2006.09.26
Ruby Is an Object-Oriented Language  (0) 2006.09.17
Roadmap  (0) 2006.09.16
top