티스토리 뷰

오늘은 다중 스레드 환경에서 자주 사용되는 전략인 COW(Copy-On-Write) 를 Java의 CopyOnWriteArrayList 통해 알아보겠습니다.


1. COW란?

COW는 Copy-On-Write의 약자로, 쓰기 작업 시 데이터의 복사본을 만들어 수정 후 원본을 교체하는 전략입니다. 읽기 작업은 기존 데이터를 그대로 사용해 락 없이 빠르고 안전하며 다중 스레드 환경에서 읽기 작업이 많을 때 유용하게 사용됩니다.

아래 흐름도를 통해 간단히 알아보겠습니다.

 

쓰기 요청

1. 쓰기 요청 시 ReentrantLock을 획득합니다.

2. 이후 기존 배열을 복사하고, 복사본에 요소를 추가/수정 후 원본 배열을 교체합니다.

3. 원본 배열 교체 후, 락을 해제해 쓰기를 완료합니다.

 

읽기 요청

1. 읽기 요청은 락 없이 배열 스냅샷을 반환해 빠르고 안전합니다.

 

 

 

 

 

 


2. CopyOnWriteArrayList 사용

Java의 CopyOnWriteArrayList는 COW 기법을 사용한 주요 사례입니다. 대표적은 예제들을 통해 간단히 알아보겠습니다.

예제1. 이벤트 리스터

import java.util.concurrent.CopyOnWriteArrayList;

interface Listener {
    void onEvent();
}

class MyListener implements Listener {
    public void onEvent() { System.out.println("Event triggered!"); }
}

CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
listeners.add(new MyListener());
new Thread(() -> listeners.add(new MyListener())).start(); // 다른 스레드에서 리스너 추가
for (Listener l : listeners) { l.onEvent(); } // 스냅샷 기반으로 안전한 순회
  • 이 예제는 다중 스레드 환경에서 이벤트 리스너를 관리하는 상황을 보여줍니다. 추가/삭제 중에도 Iterator가 스냅샷을 사용해 안전하게 순회합니다. 쓰기 작업은 배열 복사로 처리되면, 읽기는 락 없이 빠르게 수행됩니다.

 

예제2. 로그 데이터 처리

import java.util.concurrent.CopyOnWriteArrayList;

CopyOnWriteArrayList<String> logs = new CopyOnWriteArrayList<>();
new Thread(() -> logs.add("Log entry: " + System.currentTimeMillis())).start(); // 스레드1: 로그 추가
new Thread(() -> System.out.println("Current logs: " + logs)).start(); // 스레드2: 로그 읽기
  • 이 예제는 다중 스레드 환경에서 로그 데이터를 안전하게 기록하고 조회하는 상황을 보여줍니다. 로그 추가 시 배열 복사본을 만들어 수정하므로, 다른 스레드의 읽기 작업이 중단되지 않습니다. 읽기 작업은 항상 최신 스냅샷을 사용해 빠르고 안전합니다.

3. ArrayList 와 비교

특징 CopyOnWriteArrayList ArrayList
스레드 안전성 스레드 안전 (COW 전략) 비스레드 안전
쓰기 동작 배열 복사 후 수정, 비용 높음 배열 직접 수정, 빠름
읽기 동작 락 없이 스냅샷 사용, 빠름 직접 접근, 빠름
사용 사례 읽기 중심 다중 스레드 (예, 이벤트 리스너) 단일 스레드 (예, 로컬 데이터 처리)
  • COW: 스레드 안전, 읽기 성능 우수, 쓰기 시 메모리/성능 오버헤드 -> 읽기 중심 작업에 적합
  • ArrayList : 빠른 읽기/쓰기, 메모리 효율적 -> 단일 스레드 환경에 적합

 

동시성 처리 비교

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

// ArrayList: 동시 수정 시 예외
List<String> arrayList = new ArrayList<>(Arrays.asList("A", "B"));
for (String item : arrayList) { arrayList.remove(item); } // ConcurrentModificationException

// CopyOnWriteArrayList: 안전
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(Arrays.asList("A", "B"));
for (String item : cowList) { cowList.remove(item); } // 스냅샷 기반으로 안전
  • ArrayList는 순회 중 요소를 제거하면 ConcurrentModificationException 이 발생합니다. 이는 ArrayList가 비스레드 안전하며, Iterator가 배열의 현재 상태를 추적하기 때문입니다. 반면 CopyOnWriteArrayList는 순회 시 배열의 스냅샷을 사용하므로, 다른 스레드에서 요소를 추가/삭제해도 안전하게 동작합니다. 이는 COW 전략이 쓰기 작업 시 복사본을 만들어 원본을 보호하기 때문입니다.

4. 마무리

COW는 다중 스레드 환경에서 데이터를 안전하고 효율적으로 관리하는 전략입니다. 이벤트 리스너 관리나 로그 처리 같은 시나리오에서 COW의 스레드 안전성을 활용해 안정적인 코드를 작성해볼 수 있습니다.

감사합니다.

최근에 올라온 글
Total
Today
Yesterday