You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
synchronizedList()는 전달받은 List 구현체를 SynchronizedRandomAccessList로 감싸서 반환하며, 이 래퍼 객체가 내부적으로 각 메서드 호출을 동기화 처리한 뒤 원본 리스트에 위임하는 프록시 역할을 수행한다.
Collections는 같은 방식으로 다양한 컬렉션 타입에 대해 동기화 래퍼를 생성하는 메서드를 제공한다.
제공 메서드는 다음과 같은 형태로 정리할 수 있다.
synchronizedList()
synchronizedCollection()
synchronizedMap()
synchronizedSet()
synchronizedNavigableMap()
synchronizedNavigableSet()
synchronizedSortedMap()
synchronizedSortedSet()
synchronizedXxx() 계열이 편리한 이유는 구현체를 직접 수정하지 않고도, 프록시를 끼워 넣는 방식으로 동기화를 적용할 수 있기 때문이다.
다만 synchronizedList()를 쓴다고 해서 “모든 상황에서 자동으로 안전해진다”라고 이해하면 위험할 수 있으며, 특히 반복(iteration) 같은 복합 동작은 외부 동기화가 필요한 패턴이 흔하다.
List<String> list = Collections.synchronizedList(newArrayList<>());
// 반복 자체는 여러 번의 연산으로 구성되므로, 보통 다음처럼 외부에서 한 번 더 동기화하는 방식이 안전하다.synchronized (list) {
for (Strings : list) {
System.out.println(s);
}
}
위 예시는 “개별 메서드 호출은 동기화되더라도, 반복처럼 여러 단계로 이루어지는 동작은 한 덩어리로 묶어서 보호해야 한다”는 점을 보여준다.
4. 단점
동기화 오버헤드가 발생하며, synchronized는 동시에 하나의 스레드만 실행되도록 만들기 때문에 경쟁이 많아질수록 성능 저하가 발생할 수 있다.
전체 컬렉션에 대해 거의 모든 메서드에 동기화가 적용되는 형태가 되기 쉬워, 잠금 범위가 넓어질 수 있으며, 특정 부분이나 특정 메서드만 선별적으로 동기화를 적용하는 구조를 만들기 어렵다.
동기화 래퍼는 “메서드 단위 보호”에 가까운 방식이므로, 반복/조건 검사 후 실행 같은 복합 시나리오에서는 호출자가 추가로 동기화 범위를 설계해야 하는 부담이 생길 수 있다.
1. 동시성 컬렉션
자바는 1.5 버전부터 동시성 관련 기능이 크게 확장되면서, 멀티스레드 환경을 고려해 설계된 동시성 컬렉션이 함께 제공되기 시작했다.
동시성 컬렉션은 주로 java.util.concurrent 패키지에 포함되어 있으며, 다중 스레드가 동시에 접근하는 상황을 더 안전하고 효율적으로 처리하도록 설계되어 있다.
동시성 컬렉션은 단순히 모든 메서드에 synchronized를 붙이는 방식이 아니라, 정교한 잠금 메커니즘을 통해 경쟁을 줄이고 처리량을 높이는 방향으로 구현되는 경우가 많다.
구현체에 따라 synchronized, Lock, CAS, 분할 잠금 같은 다양한 메커니즘을 섞어 사용하며, 필요한 경우에는 일부 연산만 동기화하거나 내부 구조를 분리해서 잠금 경합을 최소화하는 형태로 성능을 최적화한다.
2. 동시성 컬렉션 종류
1) List
ArrayList는 멀티스레드 환경에서 안전하지 않기 때문에, 읽기 위주이고 쓰기 빈도가 낮은 상황에서는 CopyOnWriteArrayList를 대안으로 고려할 수 있다.
CopyOnWriteArrayList는 변경 시점에 내부 배열을 복사하는 방식으로 동작하기 때문에, 반복 중에도 구조 변경으로 인한 예외가 줄어들고 읽기 성능이 매우 안정적이라는 특성이 있다.
2) Set
HashSet의 대안으로 CopyOnWriteArraySet을 사용할 수 있으며, 이는 내부적으로 CopyOnWriteArrayList 기반으로 동작하는 특성을 가진다.
TreeSet의 대안으로 ConcurrentSkipListSet을 사용할 수 있으며, 이 구현체는 정렬된 순서를 유지하고 필요하면 Comparator를 사용할 수 있다.
3) Map
HashMap의 대안으로 ConcurrentHashMap을 사용할 수 있으며, 멀티스레드 환경에서 가장 대표적으로 사용되는 동시성 맵 구현체이다.
TreeMap의 대안으로 ConcurrentSkipListMap을 사용할 수 있으며, 이 구현체는 정렬된 순서를 유지하고 필요하면 Comparator를 사용할 수 있다.
4) Queue
ConcurrentLinkedQueue는 동시성 큐이며, 비차단(non-blocking) 방식으로 동작하는 것이 핵심 특징이다.
비차단 큐는 스레드가 락을 잡고 기다리기보다는, CAS 같은 방식으로 경쟁을 줄이며 높은 동시성 처리량을 목표로 한다.
5) Deque
ConcurrentLinkedDeque는 동시성 덱이며, 큐와 마찬가지로 비차단(non-blocking) 방식으로 동작한다.
양쪽 끝에서 삽입/삭제가 가능하므로, 작업 훔치기(work-stealing) 같은 패턴을 구성할 때도 유용할 수 있다.
6) 참고
LinkedHashSet, LinkedHashMap처럼 입력 순서를 유지하는 Set, Map 구현체는 멀티스레드 환경에서 바로 쓸 수 있는 동시성 버전이 기본 제공되지 않는다.
이런 요구가 있다면 Collections.synchronizedXXX() 같은 래퍼를 통해 동기화를 적용해야 하며, 이 경우에는 동기화 범위가 넓어져 성능 저하 가능성을 함께 고려해야 한다.
7) BlockingQueue
A. ArrayBlockingQueue`
ArrayBlockingQueue는 크기가 고정된 블로킹 큐이며, 큐가 꽉 차면 생산자는 대기하고 큐가 비면 소비자는 대기하는 방식으로 동작한다.
공정(fair) 모드를 사용할 수 있으며, 공정 모드를 사용하면 대기 중인 스레드에게 기회를 더 균등하게 배분할 수 있지만 그만큼 성능이 저하될 수 있다.
B. LinkedBlockingQueue
LinkedBlockingQueue는 크기를 무한으로 둘 수도 있고, 필요하면 고정 크기로 제한할 수도 있는 블로킹 큐다.
내부 구조가 연결 리스트 기반이므로, 배열 기반인 구현체와는 메모리/성능 특성이 다르게 나타날 수 있다.
C. PriorityBlockingQueue
PriorityBlockingQueue는 우선순위가 높은 요소를 먼저 처리하는 블로킹 큐이며, 일반적인 FIFO 큐와 달리 정렬 규칙에 따라 꺼내는 순서가 결정된다.
우선순위 큐 특성상 처리 순서가 입력 순서와 다를 수 있다는 점을 전제로 사용해야 한다.
D. SynchronousQueue
SynchronousQueue는 데이터를 저장하지 않는 블로킹 큐이며, 생산자가 데이터를 넣으면 소비자가 그 데이터를 받을 때까지 생산자가 대기한다.
이 구현체는 생산자와 소비자 사이에 버퍼가 없는 직접 핸드오프(hand-off) 메커니즘을 제공하며, 쉽게 말해 중간 큐에 쌓아두지 않고 생산자와 소비자가 바로 교환하는 구조로 이해할 수 있다.
F. DelayQueue
DelayQueue는 지연된 요소를 처리하는 블로킹 큐이며, 각 요소는 지정된 지연 시간이 지난 뒤에만 소비될 수 있다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
동시성 컬렉션이 필요한 이유3 - 동기화
1. 코드
elementData를 사용하는 모든 메소드에synchronized키워드를 통하여 임계영역을 설정하였다.synchronized가 적용되면 해당 인스턴스(this)의 모니터 락을 기준으로 동기화가 수행되며, 동시에 여러 스레드가 add()에 진입해 size 가 꼬이거나 같은 인덱스에 덮어쓰는 상황을 막을 수 있다.[A, B]와size = 2가 출력되어 정상 처리된 것을 확인할 수 있다.add()를 호출하더라도synchronized로 인해add()는 항상 한 스레드씩 순서대로 실행되며, 그 결과elementData[0]에는"A",elementData[1]에는"B"가 저장되고size도 2로 일관되게 증가한다.toString()에도synchronized가 적용되어 있으므로, 메인 스레드가 출력 문자열을 만들 때 다른 스레드의add()와 겹치지 않으며 출력 시점에 일관된 스냅샷을 보장한다.2. 문제
synchronized기능을 추가한 클래스를 생성하기에는 비효율적이다.동시성 컬렉션이 필요한 이유4 - 프록시 도입
1. 프록시
2. 코드
SimpleList인터페이스를 그대로 구현한다.SimpleList타입의 실제 구현체를 주입받고, 내부에target으로 보관한다.synchronized키워드를 적용한 뒤, 내부에서target의 동일한 메서드를 호출하는 구조를 만든다는 점이다.target에게 위임하는 형태를 취한다.BasicList같은 구현체를 호출하는 방식이었다면, 프록시 구조에서는 클라이언트가 프록시를 먼저 호출하고 프록시가 구현체를 호출하는 흐름이 된다.[A, B]로 정상 반영되었고, 출력 문자열 끝에by SimpleProxyList가 붙으면서 프록시가 호출 경로에 포함되어 있음을 확인할 수 있다.3. 프록시 구조 분석
1) 정적 의존관계
test()메서드는SimpleList인터페이스에만 의존하도록 구성되어 있으며, 이는 구현체가 아니라 추상화에 의존하는 구조를 만든다는 의미이다.SimpleList구현체가BasicList에서 다른 구현체로 변경되더라도, 클라이언트 코드는 인터페이스만 바라보기 때문에 수정 범위가 줄어들고 구조가 유연해진다.2) 런타임 의존 관계
SimpleList라는 인터페이스 타입만 보이지만, 런타임 시점에는 실제로 어떤 구현체 인스턴스를 참조하고 있는지에 따라 의존 관계가 구체화된다.3) 런타임 예제 - BasicList
test(new BasicList())처럼 프록시 없이 직접 구현체를 전달하면, 클라이언트는 곧바로 BasicList 인스턴스를 참조하고 해당 인스턴스의 메서드를 호출하게 된다.4) 런타임 예제 - SyncProxyList
test(new SyncProxyList(new BasicList()))처럼 프록시를 감싸서 전달하면, 클라이언트는 구현체가 아니라 프록시 인스턴스를 참조하게 된다.5) 런타임 예제 - SyncProxyList - add()
SyncProxyList.add()같은 프록시 메서드를 거치면, 해당 메서드 진입 시점에 락을 획득하고 메서드 종료 시점에 락을 해제하는 흐름이 자동으로 적용된다.4. 프록시 정리
SimpleProxyList는SimpleList를 구현하기 때문에, 내부target으로 어떤 구현체가 전달되더라도 동일한 방식으로 동작한다.synchronized로 동기화 락을 적용한 상태에서 구현체를 호출하므로, 호출 경로 기준으로는 구현체 메서드가 항상 락이 잡힌 상태에서 실행된다.SyncProxyList를 통해 구현체 코드를 전혀 손대지 않고도 동기화를 적용할 수 있다.SimpleList)를 구현한다면, 프록시 구조는 그대로 재사용할 수 있다.5. 프록시 패턴
자바 동시성 컬렉션1 - synchronized
1. 컬렉션 프레임워크의 동기화
synchronized기반의 방식은 동시에 하나의 스레드만 접근 가능하도록 제한하기 때문에 경쟁이 심한 상황에서는 처리량이 눈에 띄게 감소할 수 있다.Vector처럼 메서드 단위로synchronized가 걸려 있는 컬렉션은 이런 이유로 현대 코드에서는 잘 사용하지 않는 흐름이 있으며, 보통은 더 적합한 동시성 컬렉션이나 필요한 범위의 동기화를 선택하는 방식이 선호된다.2. 자바에서의 프록시를 이용한 컬렉선 프레임워크
Collections.synchronizedList(new ArrayList<>())메서드를 통해 동기화가 적용된ArrayList를 생성하는 방식이며, 내부적으로는 프록시(래퍼) 객체를 하나 더 감싸서 동기화 기능을 제공한다.SynchronizedRandomAccessList객체가 생성되어 반환되는 것을 확인할 수 있다.ArrayList가 바뀌는 것이 아니라,ArrayList를 감싼 동기화 프록시 객체가 만들어지는 구조라고 이해하는 편이 자연스럽다.3. 분석
synchronizedList()는 전달받은 List 구현체를SynchronizedRandomAccessList로 감싸서 반환하며, 이 래퍼 객체가 내부적으로 각 메서드 호출을 동기화 처리한 뒤 원본 리스트에 위임하는 프록시 역할을 수행한다.Collections는 같은 방식으로 다양한 컬렉션 타입에 대해 동기화 래퍼를 생성하는 메서드를 제공한다.synchronizedList()synchronizedCollection()synchronizedMap()synchronizedSet()synchronizedNavigableMap()synchronizedNavigableSet()synchronizedSortedMap()synchronizedSortedSet()synchronizedXxx()계열이 편리한 이유는 구현체를 직접 수정하지 않고도, 프록시를 끼워 넣는 방식으로 동기화를 적용할 수 있기 때문이다.synchronizedList()를 쓴다고 해서 “모든 상황에서 자동으로 안전해진다”라고 이해하면 위험할 수 있으며, 특히 반복(iteration) 같은 복합 동작은 외부 동기화가 필요한 패턴이 흔하다.4. 단점
synchronized는 동시에 하나의 스레드만 실행되도록 만들기 때문에 경쟁이 많아질수록 성능 저하가 발생할 수 있다.1. 동시성 컬렉션
java.util.concurrent패키지에 포함되어 있으며, 다중 스레드가 동시에 접근하는 상황을 더 안전하고 효율적으로 처리하도록 설계되어 있다.synchronized를 붙이는 방식이 아니라, 정교한 잠금 메커니즘을 통해 경쟁을 줄이고 처리량을 높이는 방향으로 구현되는 경우가 많다.2. 동시성 컬렉션 종류
1) List
2) Set
3) Map
4) Queue
5) Deque
6) 참고
7) BlockingQueue
A. ArrayBlockingQueue`
B. LinkedBlockingQueue
C. PriorityBlockingQueue
D.
SynchronousQueueF. DelayQueue
3. 코드
1) List 예시
2) Set 예시
3) Map 예시
4. 정리
Beta Was this translation helpful? Give feedback.
All reactions