CopyOnWriteArrayList — 쓰기 시 복사 동시성 리스트
CopyOnWriteArrayList — 쓰기 시 복사 동시성 리스트
CopyOnWriteArrayList<E>는 모든 변경 연산이 내부 배열의 새 복사본을 만들어 수행되는 스레드 안전한 ArrayList 변형이에요. 읽기가 압도적으로 많고 쓰기가 드문 상황에서 유용해요.
본문
개념 이해하기
CopyOnWriteArrayList는 모든 변경 연산(add, set 등)이 내부 배열의 새 복사본을 만들어 수행되는 스레드 안전한 ArrayList 변형이에요.
public class CopyOnWriteArrayList<E>
extends Object
implements List<E>, RandomAccess, Cloneable, Serializable
- 보통은 너무 비싸지만, 순회(traversal) 연산이 변경(mutation)을 압도적으로 많이 앞서는 경우 대안보다 효율적일 수 있어요. 순회를 동기화할 수 없거나 하고 싶지 않으면서도, 동시 스레드 간 간섭을 막아야 할 때 유용해요.
- "스냅샷" 방식 iterator: iterator 생성 시점의 배열 상태 참조를 사용해요. 이 배열은 iterator 수명 동안 절대 변하지 않으므로 간섭이 불가능하고,
ConcurrentModificationException을 던지지 않음이 보장돼요. iterator는 생성 이후의 추가·제거·변경을 반영하지 않아요. iterator 자체의remove,set,add연산은 지원되지 않으며UnsupportedOperationException을 던져요. - 모든 요소가 허용돼요(
null포함).
메모리 일관성 효과: 다른 동시성 컬렉션처럼, 한 스레드가 리스트에 객체를 넣기 전의 동작은 다른 스레드가 그 요소를 접근·제거한 이후의 동작보다 happen-before 관계예요.
생성자
public CopyOnWriteArrayList() — 빈 리스트를 만들어요.
public CopyOnWriteArrayList(Collection<? extends E> c) — 주어진 컬렉션의 iterator 순서대로 요소를 담은 리스트를 만들어요.
NullPointerException
public CopyOnWriteArrayList(E[] toCopyIn) — 주어진 배열의 복사본을 내부 배열로 가진 리스트를 만들어요.
NullPointerException
조회·탐색
public int size() / public boolean isEmpty() — 표준.
public boolean contains(Object o) — Objects.equals(o, e)인 요소가 있으면 true.
public int indexOf(Object o) — 첫 번째 일치 요소의 인덱스, 없으면 -1.
public int indexOf(E e, int index) — index부터 앞으로 검색한 첫 일치 인덱스, 없으면 -1.
IndexOutOfBoundsException(음수 인덱스)
public int lastIndexOf(Object o) — 마지막 일치 요소의 인덱스, 없으면 -1.
public int lastIndexOf(E e, int index) — index부터 뒤로 검색한 마지막 일치 인덱스, 없으면 -1.
IndexOutOfBoundsException(인덱스가 size 이상)
public E get(int index) — 지정 위치의 요소.
IndexOutOfBoundsException
public E getFirst() / public E getLast() — 첫/마지막 요소. (JDK 21+)
NoSuchElementException(비어 있을 때)
public Object clone() — 이 리스트의 얕은 복사본을 반환해요 (요소 자체는 복사되지 않음).
변경 연산
public E set(int index, E element) — 지정 위치의 요소를 교체해요. 이전 요소를 반환.
IndexOutOfBoundsException
public boolean add(E e) — 리스트 끝에 추가해요.
public void add(int index, E element) — 지정 위치에 삽입하고 이후 요소를 오른쪽으로 밀어요.
IndexOutOfBoundsException
public void addFirst(E e) / public void addLast(E e) — 첫/마지막 요소로 추가해요. (JDK 21+)
public E remove(int index) — 지정 위치의 요소를 제거하고 이후 요소를 왼쪽으로 밀어요. 제거된 요소를 반환.
IndexOutOfBoundsException
public E removeFirst() / public E removeLast() — 첫/마지막 요소를 제거·반환. (JDK 21+)
NoSuchElementException
public boolean remove(Object o) — 첫 번째 일치 요소를 제거해요. 제거됐으면 true.
public boolean addIfAbsent(E e) — 없으면 요소를 끝에 추가해요. 추가됐으면 true.
public int addAllAbsent(Collection<? extends E> c) — 아직 없는 요소만 컬렉션 iterator 순서대로 끝에 추가해요. 추가된 요소 수를 반환.
NullPointerException
public void clear() — 모든 요소를 제거해요.
일괄 연산
public boolean addAll(Collection<? extends E> c) — 모든 요소를 끝에 추가해요.
public boolean addAll(int index, Collection<? extends E> c) — 지정 위치부터 삽입해요.
IndexOutOfBoundsException,NullPointerException
public boolean containsAll(Collection<?> c) — 모든 요소 포함 시 true. NullPointerException.
public boolean removeAll(Collection<?> c) / public boolean retainAll(Collection<?> c) — 내부 임시 배열이 필요해 특히 비싼 연산이에요.
그 외
public String toString() — [a, b, c] 형태의 문자열 표현.
public boolean equals(Object o) — 같은 List이고 같은 순서·같은 요소면 true.
public int hashCode() — List.hashCode() 정의를 사용한 해시 코드.
public void forEach(Consumer<? super E> action) / public boolean removeIf(Predicate<? super E> filter) — 표준 컬렉션 연산.
public List<E> subList(int fromIndex, int toIndex) — 범위 뷰. 백킹 리스트를 이 뷰 외의 방식으로 수정하면 동작이 정의되지 않아요.
IndexOutOfBoundsException
public List<E> reversed() — 역순 뷰. 수정은 write-through돼요. (JDK 21+)
iterator/spliterator
public Iterator<E> iterator() — 생성 시점의 리스트 상태 스냅샷을 제공하는 iterator. 순회 중 동기화가 필요 없고, remove를 지원하지 않아요.
public ListIterator<E> listIterator() — 순서대로 요소를 제공하는 리스트 iterator. 스냅샷이며 remove, set, add를 지원하지 않아요.
public ListIterator<E> listIterator(int index) — 지정 위치에서 시작하는 리스트 iterator.
IndexOutOfBoundsException
public Spliterator<E> spliterator() — IMMUTABLE, ORDERED, SIZED, SUBSIZED를 보고하는 spliterator. 생성 시점 스냅샷. (JDK 1.8+)