java.util 패키지의 ArrayList와 같은 컬렉션은 스레드 세이프할까?
- 당연히 그렇지 않다.
- 내부 동작이 모두 원자적 연산으로 구현이 되어 있지 않기 때문
ArrayList와 같은 컬렉션을 스레드 세이프하게 사용하려면 어떻게 해야 하는가?
- ArrayList와 동일한 동작을 수행하는 컬렉션을 직접 구현한다.
- 그 이후, ArrayList 내부 add, size, get 등의 함수에 synchronized 키워드를 추가
이러한 컬렉션이 여러개라고 할 때 효율적으로 개발하는 방법은?

- 프록시 패턴을 도입하는 방법이 있음.
- 예를 들어, SimpleList라는 인터페이스가 있고, 이 인터페이스를 구현한 basicList, SyncProxyList를 생성한다.
- BasicList는 ArrayList와 동일한 기능을 하고 있고, 동시성 문제 해결이 필요함.
- SyncProxyList는 SimpleList를 구현하되, 모든 함수가 Synchronized로 되어 있음.
- 이 때, SyncProxyList가 BasicList를 감싸는 형태라면 BasicList도 동시성 문제가 해결될 수 있음.
- BasicList와 같은 클래스가 여러개 일 경우 이 여러개는 모두 SyncProxyList로 감싸져 동시성 문제를 해결할 수 있음.
자바에서 제공하는 synchronized 프록시에 대해 설명하시오
List<String> list = Collections.synchronizedList(new ArrayList<>());
- 위와 같이 Collections를 사용해서 ArrayList와 같은 컬렉션을 Synchronize 프록시를 적용할 수 있음.
- 이러면 클라이언트 -> SynchronizedRandomAccessList(프록시) -> ArrayList와 같이 동작함.
- 하지만 이 방식 역시 여러 단점이 존재함.
- 첫째, 동기화 오버헤드가 발생한다. 비록 synchronized 키워드가 멀티스레드 환경에서 안전한 접근을 보장하 지만, 각 메서드 호출 시마다 동기화 비용이 추가된다. 이로 인해 성능 저하가 발생할 수 있다.
둘째, 전체 컬렉션에 대해 동기화가 이루어지기 때문에, 잠금 범위가 넓어질 수 있다. 이는 잠금 경합(lock contention)을 증가시키고, 병렬 처리의 효율성을 저하시키는 요인이 된다. 모든 메서드에 대해 동기화를 적용하 다 보면, 특정 스레드가 컬렉션을 사용하고 있을 때 다른 스레드들이 대기해야 하는 상황이 빈번해질 수 있다. - 셋째, 정교한 동기화가 불가능하다. synchronized 프록시를 사용하면 컬렉션 전체에 대한 동기화가 이루어지 지만, 특정 부분이나 메서드에 대해 선택적으로 동기화를 적용하는 것은 어렵다. 이는 과도한 동기화로 이어질 수 있다.
- 첫째, 동기화 오버헤드가 발생한다. 비록 synchronized 키워드가 멀티스레드 환경에서 안전한 접근을 보장하 지만, 각 메서드 호출 시마다 동기화 비용이 추가된다. 이로 인해 성능 저하가 발생할 수 있다.
- 즉, Lock을 걸어서 동시성 문제를 해결하는 것은 성능 저하를 불러일으킬 수 있고, 이에 따라 정교하게 필요한 부분만 딱 Lock을 걸어서 작업을 해야함.
- synchornized 프록시와 같은 방법을 사용하면 정교한 컨트롤이 되지 않고 Lock의 범위가 넓어져 성능 저하를 야기할 수 있음.
동시성 컬렉션에 대해 설명하시오
- java.util.concurrent 패키지는 고성능 멀티스레드 환경을 지원하는 다양한 동시성 컬렉션 클래스를 제공함
- 이 컬렉션들은 더 정교한 잠금 메커니즘을 사용해 동시 접근을 효율적으로 처리하며, 필요한 경우 일부 메서드에만 동기화를 적용하는 유연한 동기화 전략을 제공함.
- synchronized, lock(reentarantlock), CAS, 분할 잠금 등 다양한 기법이 도입 됨.
- 자세한 구현은 복잡해 이해하기보다는 잘 활용하는 방법이 중요
자바 동시성 컬렉션 정리 표
| 종류 | 구현체 | 설명 | 기존 컬렉션 | 특징/ 사용사례 |
| List | CopyOnWriteArrayList | 데이터 변경 시 내부 배열을 복사하여 새로 생성. 읽기 작업이 많고 쓰기 작업이 적을 때 효율적 | ArrayList | 스레드 안전한 리스트 |
| Set | CopyOnWriteArraySet | 내부적으로 CopyOnWriteArrayList를 사용하여 구현된 동시성 Set | HashSet | 쓰기 작업이 적고 읽기 작업이 많을 때 사용 |
| ConcurrentSkipListSet | 정렬된 순서를 유지하며, 동시성 환경에서도 안전. Comparator 사용 가능 | TreeSet | 정렬이 필요한 Set | |
| Map | ConcurrentHashMap | 비 차단(non-blocking) 해시맵으로, 락을 세분화하여 높은 성능을 보장 | HashMap | 스레드 안전한 맵 |
| ConcurrentSkipListMap | 정렬된 순서를 유지하며, 동시성 환경에서도 안전. Comparator 사용 가능 | TreeMap | 정렬이 필요한 Map | |
| Queue | ConcurrentLinkedQueue | 비 차단(non-blocking) 동시성 큐로, FIFO(선입선출) 방식으로 동작 | - | 무한 크기 큐 |
| Deque | ConcurrentLinkedDeque | 비 차단(non-blocking) 동시성 데크로, 양쪽에서 삽입/삭제 가능 | - | 무한 크기 데크 |
| BlockingQueue | ArrayBlockingQueue | 크기가 고정된 블로킹 큐. 공정(fair) 모드를 지원함 | - | 제한된 리소스를 관리할 때 사용 |
| LinkedBlockingQueue | 크기가 무한하거나 고정된 블로킹 큐 | - | 일반적인 생산자-소비자 패턴 | |
| PriorityBlockingQueue | 우선순위가 높은 요소를 먼저 처리하는 블로킹 큐 | - | 우선순위 작업 처리 시 사용 | |
| SynchronousQueue | 데이터를 저장하지 않고 직접 전달하는 블로킹 큐 | - | 생산자-소비자 직접 거래 | |
| DelayQueue | 요소가 지연 시간이 지난 후에야 소비될 수 있는 블로킹 큐 | - | 작업 스케줄링, 지연 작업 처리 |
'김영한의 실전 자바 - 고급 1편' 카테고리의 다른 글
| 김영한의 실전 자바 - 고급 1편(총 정리) (0) | 2026.01.26 |
|---|---|
| 김영한의 실전 자바 - 고급 1편(Executor) (0) | 2025.01.13 |
| 김영한의 실전 자바 - 고급 1편(CAS) (1) | 2025.01.11 |
| 김영한의 실전 자바 - 고급 1편(생산자 소비자 문제: Object - wait/notify, ReentarantLock - await/signal, BlockingQueue) (1) | 2025.01.08 |
| 김영한의 실전 자바 - 고급 1편(volatile, synchronized, LockSupport, ReentrantLock) (1) | 2025.01.05 |