Java에서 ArrayList와 CopyOnWriteArrayList는 모두 List 인터페이스를 구현한 대표적인 클래스입니다. 하지만 내부 동작 방식과 설계 목적이 다르기 때문에 여러 중요한 차이점을 가지고 있습니다.
아래 표는 두 클래스의 주요 차이점을 항목별로 정리한 것입니다.
ArrayList와 CopyOnWriteArrayList의 주요 차이점
| 번호 | 구분 | ArrayList | CopyOnWriteArrayList |
|---|---|---|---|
| 1 | 동기화(Synchronization) | 기본적으로 동기화되지 않습니다(non-synchronized). | 동기화되어 있어 한 번에 하나의 스레드만 객체에 접근할 수 있습니다. |
| 2 | 성능(Performance) | 동기화 오버헤드가 없어 처리 속도가 빠릅니다. | 동기화 및 복사 작업으로 인해 성능이 상대적으로 느립니다. |
| 3 | Fail-fast / Fail-safe | 반복자(Iterator)가 fail-fast 방식으로 동작하며, 순회 중 컬렉션이 수정되면 ConcurrentModificationException이 발생합니다. | fail-safe 방식으로 동작하며, 순회 중에도 컬렉션의 수정이 허용됩니다. |
| 4 | 요소 제거(Remove) | 반복(iteration) 중에도 반복자를 통해 remove() 연산을 수행할 수 있습니다. | 반복 중에는 remove() 연산을 수행할 수 없으며, 시도할 경우 런타임 예외인 UnsupportedOperationException이 발생합니다. |
| 5 | 도입 시기 | Java 1.2 버전부터 제공된 오래된 클래스입니다. | Java 5(버전 1.5)에서 추가된 비교적 최신 클래스입니다. |
| 6 | 패키지(Package) | java.util 패키지에 포함되어 있습니다. | java.util.concurrent 패키지에 포함되어 있습니다. |
예제 1: ArrayList 사용 예시
다음 예제는 ArrayList의 반복자를 사용해 순회 중에 요소를 안전하게 제거하는 코드입니다.
import java.util.*;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> l = new ArrayList<>();
l.add("A");
l.add("B");
l.add("C");
Iterator<String> itr = l.iterator();
while (itr.hasNext()) {
String s = itr.next();
if (s.equals("B")) {
itr.remove(); // 정상적으로 제거됨
}
}
System.out.println(l);
}
}실행 결과
[A, C]
위 코드에서 볼 수 있듯이, ArrayList의 반복자는 순회 중 remove() 메서드 호출을 지원하며 예외 없이 정상적으로 실행됩니다.
예제 2: CopyOnWriteArrayList 사용 예시
반면 CopyOnWriteArrayList의 반복자는 읽기 전용 스냅샷을 기반으로 동작하기 때문에, 순회 중 remove()를 호출하면 예외가 발생합니다.
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.*;
class CopyOnWriteArrayListDemo extends Thread {
static CopyOnWriteArrayList<String> l = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
l.add("A");
l.add("B");
l.add("C");
Iterator<String> itr = l.iterator();
while (itr.hasNext()) {
String s = itr.next();
System.out.println(s);
if (s.equals("B")) {
// RuntimeException 발생
itr.remove();
}
Thread.sleep(1000);
}
System.out.println(l);
}
}실행 결과
A B Exception in thread "main" java.lang.UnsupportedOperationException
정리: 어떤 클래스를 선택해야 할까?
- 단일 스레드 환경 또는 외부에서 동기화를 직접 관리할 수 있는 경우라면, 빠른 성능을 제공하는 ArrayList가 적합합니다.
- 멀티 스레드 환경에서 읽기 작업이 쓰기 작업보다 훨씬 많은 경우에는 스레드 안전성을 보장하는 CopyOnWriteArrayList가 유리합니다. 단, 쓰기 작업 시마다 내부 배열 전체가 복사되므로 잦은 수정이 일어나는 상황에서는 비효율적일 수 있습니다.
두 클래스의 특성을 정확히 이해하고 애플리케이션의 동시성 요구 사항에 맞게 선택하는 것이 중요합니다.