관리 메뉴

백엔드 엔지니어 이재혁

[Java] 컬렉션 프레임워크와 동시성 본문

Java

[Java] 컬렉션 프레임워크와 동시성

alex00728 2025. 6. 13. 20:50

기본 컬렉션 프레임워크는 동시성 문제를 일으킬 수 있다.

 

예시) `ArrayList`의 `add()` 메서드는 데이터를 추가할 뿐 아니라, `size`에 새로운 값도 대입하는 등 원자적인 연산이 아니다.

// ArrayList 클래스의 add 메서드들 중 하나
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

 

해결 방법

1. 프록시 패턴

동시성 처리가 되지 않은 기본 객체는 그대로 두고, 아래와 같이 그 객체의 인터페이스 구현체를 만든다.

public class SyncProxyList implements SimpleList {

    private SimpleList target;

    public SyncProxyList(SimpleList target) {
        this.target = target;
    }
    
    @Override
    public synchronized int size() {
        return this.target.size();
    }
    // ...
}

 

위 `size()` 메서드와 같이 메서드들에 `synchronized` 키워드를 붙이고,

`target`의 메서드를 `synchronized` 내부 메서드 안에서 호출하도록 한다.

 

사용 예시) 아래와 같이 기본 객체를 생성하고, 그 객체를 프록시 객체의 매개변수로 전달해준다.

SimpleList syncedList = new SyncProxyList(new BasicList());

 

Java 기본 패키지 사용

java.util 패키지의 `Collections` 클래스에서 제공하는 프록시 기능

List<String> list = Collections.synchronizedList(new ArrayList<>());

 

`List` 뿐 아니라 다른 컬렉션 객체들도 사용할 수 있다. `Collections.synchronizedxxx(new xxx());`

각 리스트/맵/해시맵 등의 기본 객체들의 메서드에 프록시 패턴으로 `synchronized`를 걸어준다.

 

한계

다만, 이전에 배운 동시성 제어에서도 느꼈지만, synchronized를 무식하게 거는 방법은 성능 저하가 크다.

특히 컬렉션 프레임워크 메서드 안에서도 임계 구역을 좁힐 수 있는 여지가 있는데도 작용하지 못하고, CAS 연산 등의 다양한 성능 개선 방법을 적용시킬 수 없다.

 

2. 동시성 컬렉션 프레임워크

동시성 제어 최적화를 하지 못하는 proxy 패턴의 synchronized 적용법 대신 동시성 제어를 하면서도 성능 최적화도 해낸 기본 패키지를 사용할 수 있다.

 

List, Set, Map

종류 기본 자료구조 기타
List CopyOnWriteArrayList ArrayList  
Set CopyOnWriteArraySet HashSet  
ConcurrentSkipListSet TreeSet 정렬 상태 유지 /  Comparator 가능
Map ConcurrentHashMap HashMap  
ConcurrentSkipListMap TreeMap 정렬 상태 유지 /  Comparator 가능

 

Queue (Deque)

종류 특징 기타
Queue ConcurrentLinkedQueue 동시성 큐, 논블로킹 큐  
Deque ConcurrentLinkedDeque 동시성 데크, 논블로킹 큐  
BlockingQueue
(스레드 차단)
ArrayBlockingQueue 크기가 고정된 블로킹 큐 공정(fair)모드 사용 가능
LinkedBlockingQueue 크기가 무한하거나 고정된 블로킹 큐  
PriorityBlockingQueue 우선순위가 높은 요소를 먼저 처리하는 블로킹 큐  

 

추가 BlockingQueue

`SynchronousQueue`

데이터를 저장하지 않는 블로킹 큐로, 생산자가 데이터를 추가하면 소비자가 그 데이터를 받을 때까지 대기한다. 생산자-소비자 간의 직접적인 핸드오프(hand-off) 메커니즘을 제공한다. 쉽게 이야기해서 중간에 큐 없이 생산자, 소비자가 직접 거래한다.

 

`DelayQueue`

지연된 요소를 처리하는 블로킹 큐로, 각 요소는 지정된 지연 시간이 지난 후에야 소비될 수 있다. 일정 시간이 지난 후 작업을 처리해야 하는 스케줄링 작업에 사용된다.

 

 

자료 구조 다시 정리

자료구조 내용을 다시 한 번 정리해야 할 것 같아서 Java의 자료구조 내용을 전체적으로 다시 정리해봤다.

Java 자료구조 글

'Java' 카테고리의 다른 글

[JUnit] Controller 단위 테스트 작성하기, 그리고 회고  (1) 2025.06.29
[Java] Executor 프레임워크 기본  (0) 2025.06.25
[JAVA] CAS 락 구현  (0) 2025.06.10
[Java] CAS 연산 활용  (1) 2025.06.09
[Java] Atomic과 CAS 연산  (0) 2025.06.09