안전한 스레드를 위한 CopyOnWriteArrayList

TL;DR

List를 읽기 위해 전달할 때, 수정할 일이 거의 없고 주로 순회가 일어나는 용도로 사용할 때는 안전한 스레드 처리를 위해 CopyOnWriteArrayList를 사용하자.


ArrayListsynchronized를 이용한 동시성 제어

ArrayList는 스레드에 안전하게 설계되지 않았기 때문에 자바 1.5 이전에는
synchronized와 함께 사용하여 동시성 제어를 해주었다.
synchronized 키워드는 해당되는 메서드나 블록을 한 번에 한 스레드씩 수행하도록 보장해주는 역할을 한다.

그러나 각각의 스레드가 과도하게 필요 이상으로 멈추거나 기다려야 하는 상황을 만들게 된다.
따라서 synchronized를 이용한 과도한 동기화는
성능을 떨어뜨릴 수 있고 교착 상태에 빠뜨릴 수 있으며, 예측할 수 없는 동작을 유발하기도 한다.

CopyOnWriteArrayList

자바 1.5부터는 안전한 스레드 처리를 위해 CopyOnWriteArrayList를 사용할 수 있다.
자바의 동시성 컬렉션 라이브러리(java.util.concurrent)인 CopyOnWriteArrayList에 대해 알아보자.

CopyOnWriteArrayList의 동작 방식과 특징

이름에서 알 수 있듯이 ArrayList를 구현한 클래스로서,
내부를 변경하는 작업은 항상 깨끗한 복사본을 만들어서 수행하도록 구현되어 있다.
내부의 배열은 절대 변경할 수 없으므로 순회할 때 락이 필요 없어서 속도면에서 매우 빠르다는 장점이 있다.

데이터 수정이나 삭제 등의 다른 용도로 쓰일 경우에는 속도가 느려지기 때문에
수정할 일은 거의 없으며 주로 순회가 일어나는 용도로 사용하는 것이 적합하다.

CopyOnWriteArrayList는 객체를 매번 복사하지 않고, 전달할 때 해당 상태를 스냅샷으로 가지고 있는 방식이다.

ArrayListVS CopyOnWriteArrayList

CopyOnWriteArrayListArrayList 사이에는 한 가지 차이가 있다.
CopyOnWriteArrayListArrayList와는 달리,
List를 읽기 위해 어딘가에 전달할 때 원본이 아닌 복사본을 만들어서 전달한다.

Effective Java 3/e, p425

코드 79-4: CopyOnWirteArrayList를 사용해 구현한 스레드 안전하고 관찰 가능한 집합

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private final List<SetObserver<E>> observers = new CopyOnWriteArrayList<>();

public void addObserver (SetObserver<E> observer) {
observers.add(observer);
}

public boolean removeObserver (SetObserver<E> observer) {
return observers.remove(observer);
}

private void notifyElementAdded (E element) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}

참고