본문 바로가기
Java/Java이론

CopyOnWriteArrayList란 무엇인가?

by P_eli 2024. 11. 14.
728x90
반응형

Java에서 멀티스레드 환경에서 컬렉션을 사용할 때 동시성 문제를 해결하는 것은 중요합니다. CopyOnWriteArrayList는 이러한 문제를 해결하기 위해 고안된 특별한 리스트 구현체로, java.util.concurrent 패키지에 속해 있습니다. 이 리스트는 쓰기 연산이 발생할 때 리스트의 전체 복사본을 생성하여, 읽기와 쓰기가 충돌하지 않도록 만든 리스트로, 읽기 작업이 많고 쓰기 작업이 드문 경우에 유용합니다.

그럼 이제 CopyOnWriteArrayList의 특징과 사용 사례, 내부 동작 방식을 자세히 살펴보겠습니다.

 

CopyOnWriteArrayList의 주요 특징

1. 쓰기 작업 시 리스트 복사

CopyOnWriteArrayList는 리스트에 쓰기 작업(예: add, set, remove 등)이 발생할 때, 리스트의 현재 상태를 복사하여 새로운 배열을 만듭니다. 새로운 배열에 변경 사항이 적용되고, 기존 배열은 변경되지 않습니다. 이로 인해 읽기 작업이 쓰기 작업의 영향을 받지 않으며, 동시에 여러 스레드가 안전하게 리스트를 읽을 수 있습니다.

예를 들어, 다음과 같은 코드가 있을

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Hello");
list.add("World");

위 코드를 실행하면, add 메서드 호출 시마다 새로운 배열이 만들어지고, "Hello"와 "World"가 각각 리스트에 추가됩니다. 이렇게 쓰기 작업이 일어날 때마다 리스트 전체가 복사되므로 메모리 사용량이 늘어나지만, 읽기 작업은 기존 배열을 참조하기 때문에 안정적으로 수행됩니다.

 

2. 읽기 작업과 쓰기 작업 간의 충돌 방지

CopyOnWriteArrayList의 가장 큰 장점은 읽기 작업과 쓰기 작업이 충돌하지 않는다는 점입니다. 일반적으로 동기화된 리스트에서는 쓰기 작업이 수행될 때 읽기 작업이 잠깁니다. 하지만 CopyOnWriteArrayList는 읽기 작업이 쓰기 작업의 영향을 받지 않기 때문에, 동기화 없이 읽기 작업이 가능합니다. 따라서 읽기 작업이 잦고, 쓰기 작업이 적은 경우에 유리합니다.

 

3. Iterator 사용 시 안전한 Snapshot

CopyOnWriteArrayList의 또 다른 유용한 특징은 iterator를 통해 반복할 때 안전한 스냅샷(Snapshot)을 제공한다는 점입니다. 일반적인 리스트에서는 반복 중에 리스트가 수정되면 ConcurrentModificationException이 발생할 수 있지만, CopyOnWriteArrayList는 리스트의 복사본을 반환하기 때문에 반복 중에도 안전하게 사용할 수 있습니다.

다음은 이를 보여주는 예제입니다.

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");

for (String fruit : list) {
    System.out.println(fruit);
    list.add("Date");  // ConcurrentModificationException 발생 X
}

위 코드에서 반복 중에도 "Date"를 추가해도 예외가 발생하지 않습니다. 이는 for-each 구문이 리스트의 스냅샷을 참조하기 때문에 가능한 일입니다.

 

언제 CopyOnWriteArrayList를 사용해야 할까?

CopyOnWriteArrayList는 모든 상황에 적합한 컬렉션이 아닙니다. 다음과 같은 경우에 사용하기에 적합합니다:

  • 읽기 작업이 빈번하고, 쓰기 작업이 드문 경우: 예를 들어, 이벤트 로그 기록과 같이 추가는 간헐적으로 일어나지만, 읽기가 자주 발생하는 경우에 적합합니다.
  • 데이터가 자주 변하지 않는 경우: CopyOnWriteArrayList는 쓰기 작업 시마다 전체 리스트를 복사하기 때문에, 쓰기 작업이 빈번하게 일어나면 성능 저하가 발생할 수 있습니다.
  • 데이터 무결성이 중요한 경우: 여러 스레드에서 동시에 읽기 작업을 수행하면서 안전하게 접근해야 하는 경우에 유리합니다.

CopyOnWriteArrayList 내부 동작 방식

CopyOnWriteArrayList는 기본적으로 읽기 전용 배열에 기반합니다. 쓰기 작업이 발생하면 현재 배열을 복사하여 새로운 배열을 만들고, 이 새로운 배열에 변경 사항을 적용한 후 참조를 업데이트합니다. 이 과정에서 기존 배열은 변하지 않으므로, 여러 스레드가 안전하게 읽을 수 있습니다.

이 메커니즘은 메모리와 CPU를 많이 사용하는 단점이 있지만, 동시성 제어가 중요한 상황에서는 유리한 선택이 될 수 있습니다.

CopyOnWriteArrayList 예제 코드

다음은 CopyOnWriteArrayList 사용한 간단한 예제입니다.

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("Hello");
        list.add("World");
        list.add("!");

        // 읽기 작업
        System.out.println("리스트 내용: " + list);

        // 쓰기 작업
        list.add("Java");
        System.out.println("리스트 수정 후: " + list);

        // 스냅샷을 이용한 반복
        for (String str : list) {
            System.out.println("반복 중: " + str);
            list.add("New Element"); // 예외 발생 없이 추가 가능
        }
    }
}

위 예제에서, for-each 반복문을 통해 리스트 내용을 출력하는 동안 새로운 요소를 추가해도 예외가 발생하지 않으며, 스냅샷을 통한 안전한 읽기가 보장됩니다.

 

결과

 코드의 실행 결과는 다음과 같습니다.

 

정리

CopyOnWriteArrayList는 쓰기 시 전체 복사를 통해 읽기와 쓰기 간의 충돌을 방지하고, 동시성 환경에서 안전하게 사용할 수 있는 리스트입니다. 읽기 작업이 많고 쓰기 작업이 드문 경우에 특히 효과적이며, 스냅샷 기반의 반복을 지원하여 반복 중에도 안전하게 리스트를 수정할 수 있습니다. 다만 쓰기 작업이 잦은 경우에는 성능이 저하될 수 있으므로 주의가 필요합니다.

CopyOnWriteArrayList Java 동시성 컬렉션에서 중요한 역할을 하며, 동시성 문제가 우려되는 환경에서 강력한 대안이 있습니다.

728x90
반응형