
자바 멀티스레드 프로그래밍을 하다 보면 가장 빈번하게 마주치는 문제 중 하나가 바로 '컬렉션의 동시 수정(Concurrent Modification)'입니다. 여러 스레드가 동시에 데이터를 읽고 쓰는 과정에서 데이터가 깨지거나 ConcurrentModificationException이 발생하면 개발자는 당혹감에 빠지기 쉽습니다. 이러한 문제를 우아하게 해결하기 위해 설계된 것이 바로 java.util.concurrent 패키지의 CopyOnWriteArrayList입니다. 오늘은 이 클래스가 왜 탄생했는지, 어떤 원리로 동작하는지, 그리고 실무에서 언제 사용해야 최적의 성능을 낼 수 있는지 심도 있게 살펴보겠습니다.
1. CopyOnWriteArrayList란 무엇인가?
이름에서 알 수 있듯이 "쓸 때 복사한다(Copy-On-Write)"는 전략을 취하는 ArrayList의 스레드 안전(Thread-safe) 버전입니다. 일반적인 ArrayList에 동기화 처리를 한 Vector나 Collections.synchronizedList와는 완전히 다른 메커니즘으로 동작합니다. 핵심 개념은 간단합니다. 내부의 배열을 수정(add, set, remove 등)할 때마다 기존 배열을 수정하는 것이 아니라, 배열 전체를 복사하여 새로운 배열을 만든 뒤 그곳에 수정을 반영하고 참조를 교체하는 방식입니다.
2. 동작 원리: Copy-On-Write 메커니즘
이 컬렉션의 핵심은 '불변성(Immutability)'의 활용입니다. 데이터를 읽는 스레드는 현재 스냅샷인 기존 배열을 바라보며 아무런 락(Lock) 없이 자유롭게 데이터를 읽습니다. 반면, 데이터를 쓰는 스레드는 별도의 락을 획득한 후 다음 과정을 거칩니다.
- 내부 배열의 복사본을 생성한다.
- 복사본에 요소를 추가하거나 수정한다.
- 수정이 완료된 복사본을 원래의 내부 배열 참조로 교체한다.
이 덕분에 읽기 작업은 쓰기 작업이 진행 중이더라도 멈추지 않고(Non-blocking) 수행될 수 있습니다. 이는 읽기 성능을 극대화하는 결과를 낳습니다.
3. 주요 특징 및 장단점
모든 기술이 그렇듯 CopyOnWriteArrayList 역시 만능은 아닙니다. 장점과 단점이 매우 명확한 편입니다.
| 구분 | 주요 특징 및 설명 |
|---|---|
| 장점: 읽기 성능 | 읽기 작업에 락이 전혀 필요 없어 멀티스레드 환경에서 매우 빠릅니다. |
| 장점: 예외 방지 | 반복문 순회 중에 요소를 수정해도 ConcurrentModificationException이 발생하지 않습니다. |
| 단점: 메모리 오버헤드 | 쓰기 작업 시마다 배열을 통째로 복사하므로 메모리 소비가 큽니다. |
| 단점: 쓰기 성능 저하 | 데이터 변경이 잦은 경우 배열 복사 비용 때문에 성능이 급격히 떨어집니다. |
4. ArrayList vs SynchronizedList vs CopyOnWriteArrayList
상황에 맞는 최적의 컬렉션을 선택하기 위해 세 가지 리스트를 비교해 보겠습니다.
| 비교 항목 | ArrayList | SynchronizedList | CopyOnWriteArrayList |
|---|---|---|---|
| 스레드 안전성 | 아니오 | 예 (전체 락) | 예 (순차적 쓰기/자유로운 읽기) |
| 읽기 성능 | 매우 높음 | 낮음 (대기 발생) | 매우 높음 |
| 쓰기 성능 | 매우 높음 | 낮음 (대기 발생) | 매우 낮음 (복사 비용) |
| 이터레이터 | Fail-fast | Fail-fast | Fail-safe (Snapshot 방식) |
5. 실무에서의 활용 사례 (Best Practice)
그렇다면 CopyOnWriteArrayList는 언제 사용해야 할까요? 핵심 키워드는 "읽기가 쓰기보다 압도적으로 많을 때"입니다.
- 이벤트 리스너(Listener) 관리: 이벤트 리스너 목록은 한번 등록되면 잘 바뀌지 않지만, 이벤트 발생 시마다 수시로 순회하며 호출됩니다. 이럴 때 최적입니다.
- 시스템 설정값 캐싱: 서버 기동 시 로드된 설정값이 런타임에 거의 변하지 않고 참조만 되는 경우에 적합합니다.
- 알림 발송 대상 관리: 실시간으로 변경되는 사용자 목록보다는, 특정 시점에 확정된 대상에게 안정적으로 메시지를 뿌려야 하는 경우 유용합니다.
6. 결론: 트레이드오프(Trade-off)의 이해
CopyOnWriteArrayList는 동시성 문제를 해결하기 위해 메모리와 쓰기 성능을 대가로 지불한 구조입니다. 단순히 "스레드 안전하다"는 이유만으로 모든 리스트를 이것으로 대체해서는 안 됩니다. 데이터의 변경 빈도를 냉철하게 분석하고, 안정적인 순회가 성능보다 중요한 지점에서 이 강력한 도구를 꺼내 드시기 바랍니다.
참고 문헌 및 출처
- Java Platform, Standard Edition 21 API Specification - java.util.concurrent.CopyOnWriteArrayList
- Brian Goetz, "Java Concurrency in Practice", Addison-Wesley Professional.
- Baeldung, "Guide to CopyOnWriteArrayList in Java" (2024).
'Language > Java' 카테고리의 다른 글
| [JAVA] 성능을 결정짓는 핵심 한 수, 자바 자료구조 선택 가이드 (0) | 2026.01.19 |
|---|---|
| [JAVA] 초고속 데이터 검색의 핵심, 자바 이진 탐색(Binary Search) 완벽 마스터하기 (0) | 2026.01.19 |
| [JAVA] 멀티스레드 환경의 구원자 : ConcurrentHashMap을 써야 하는 진짜 이유 (0) | 2026.01.19 |
| [JAVA] HashMap의 성능을 결정짓는 두 축 : Capacity와 Load Factor 완벽 가이드 (0) | 2026.01.19 |
| [JAVA] 런타임의 최대 적, NullPointerException(NPE)을 완벽하게 방어하는 전략 (0) | 2026.01.19 |