Skip to content

Commit f20ecbd

Browse files
committed
week4: 동시성 컬렉션
1 parent c221823 commit f20ecbd

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

Diff for: heedong/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
| 1주차 |[프로세스와 스레드](./process-and-thread.md)<br>• [스레드 생성/실행](./thread-detail.md)(Thread, Runnable)<br>• [스레드 제어/생명주기](./thread-lifecycle.md) |
55
| 2주차 |[volatile](./volatile.md)<br>• [synchronized](./synchronized.md)<br>• [concurrent.Lock](./concurrentLock.md) |
66
| 3주차 |[생산 소비자 문제](bounded-buffer.md) |
7-
| 4주차 |[CAS(Compare-And-Swap)](cas.md) |
7+
| 4주차 |[CAS(Compare-And-Swap)](cas.md)<br>• [동시성 컬렉션](concurrent-collection.md) |
88
| 5주차 | |

Diff for: heedong/concurrent-collection.md

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# 동시성 컬렉션
2+
3+
`java.util` 패키지에 소속되어 있는 컬렉션 프레임워크는 스레드 세이프(Thread Safe)하지 않음
4+
- Thread Safe: 여러 스레드가 동시에 접근해도 괜찮은 경우
5+
6+
```java
7+
public class SimpleListMainV0 {
8+
9+
public static void main(String[] args) {
10+
addAll(new BasicList());
11+
}
12+
13+
private static void test(SimpleList list) throws InterruptedException {
14+
log(list.getClass().getSimpleName());
15+
16+
// A를 리스트에 저장하는 코드
17+
Runnable addA = new Runnable() {
18+
@Override
19+
public void run() {
20+
list.add("A");
21+
log("Thread-1: list.add(A)");
22+
}
23+
};
24+
25+
// B를 리스트에 저장하는 코드
26+
Runnable addB = new Runnable() {
27+
@Override
28+
public void run() {
29+
list.add("B");
30+
log("Thread-2: list.add(B)");
31+
}
32+
};
33+
34+
Thread thread1 = new Thread(addA, "Thread-1");
35+
Thread thread2 = new Thread(addB, "Thread-2");
36+
thread1.start();
37+
thread2.start();
38+
thread1.join();
39+
thread2.join();
40+
log(list); // actual: [B, null]
41+
}
42+
}
43+
```
44+
45+
따라서 컬렉션에서도 여러 스레드에서 동시에 접근한다면 스레드 세이프한 컬렉션을 사용해야 함
46+
47+
<br>
48+
49+
### 방법 1. 프록시 패턴 활용
50+
```java
51+
public static <T> List<T> synchronizedList(List<T> list) {
52+
return new SynchronizedRandomAccessList<>(list);
53+
}
54+
```
55+
56+
컬렉션 내부의 모든 메서드를 `synchronized` 키워드를 추가하여 관리하는 것은 유지보수에 어려움이 있음
57+
58+
따라서 프록시 패턴을 활용해서 컬렉션을 감싸는 방법을 사용 (e.g. `Collections.synchronizedList()`)
59+
- 내부의 모든 메서드에 `synchronized` 키워드가 추가
60+
61+
하지만 단점도 존재함
62+
- 동기화 오버헤드 발생
63+
- `synchronized` 키워드가 멀티스레드 환경에서 안전한 접근을 보장함
64+
- 다만 각 메서드 호출 시마다 동기화 비용이 추가됨, 이로 인해 성능 저하가 발생할 수 있음
65+
- 전체 컬렉션에 대해 동기화가 이루어지기 때문에 잠금 범위가 넓어질 수 있음
66+
- 이는 잠금 경합(lock contention)을 증가시키고, 병렬 처리의 효율성을 저하시키는 요인 유발
67+
- 모든 메서드에 대해 동기화를 적용하다 보면 특정 스레드가 컬렉션을 사용하고 있을 때 다른 스레드들이 대기해야 하는 상황이 빈번해질 수 있음
68+
- 정교한 동기화가 불가능함
69+
- `synchronized` 프록시를 사용하면 컬렉션 전체에 대한 동기화가 이루어지지만 특정 부분이나 메서드에 대해 선택적으로 동기화를 적용하는 것은 어려움
70+
- 이는 과도한 동기화로 이어질 수 있음
71+
72+
따라서 이 방식은 동기화에 대한 최적화가 이루어지지 않는 구현
73+
- 자바는 이런 단점을 보완하기 위해 `java.util.concurrent` 패키지에 동시성 컬렉션(concurrent collection)을 제공
74+
75+
<br>
76+
77+
### 방법 2. 동시성 컬렉션 사용
78+
자바 1.5부터 `java.util.concurrent` 패키지에는 고성능 멀티스레드 환경을 지원하는 다양한 동시성 컬렉션 클래스들을 제공
79+
80+
#### 컬렉션 인터페이스
81+
82+
| 컬렉션 인터페이스 | 동시성 컬렉션 클래스 | 설명 |
83+
|:------------------:|:--------------:|:-----------------------:|
84+
| `List` | `CopyOnWriteArrayList` | `ArrayList`의 대안 |
85+
| `Set` | `CopyOnWriteArraySet` | `HashSet`의 대안 |
86+
| `Set` | `ConcurrentSkipListSet` | `TreeSet`의 대안 |
87+
| `Map` | `ConcurrentHashMap` | `HashMap`의 대안 |
88+
| `Map` | `ConcurrentSkipListMap` | `TreeMap`의 대안 |
89+
90+
`LinkedHashSet` , `LinkedHashMap` 처럼 입력 순서를 유지하면서 멀티스레드 환경에서 사용할 수 있는 `Set` , `Map` 구현체는 제공하지 않음
91+
- 필요하다면 `Collections.synchronizedXxx()` 를 사용해야 함
92+
- 설계 철학, 성능 이슈(내부 추가 동기화) 및 코드 복잡성 등을 고려하여 제공하지 않음
93+
94+
<br>
95+
96+
#### BlockingQueue 인터페이스
97+
98+
| 동시성 컬렉션 클래스 | 설명 |
99+
|:------------------:|:------------------------------------------------------------------------|
100+
| `ArrayBlockingQueue` | - 크기가 고정된 블로킹 큐<br>- 공정(fair) 모드를 사용할 수 있음, 다만 공정(fair) 모드를 사용 시 성능이 저하 |
101+
| `LinkedBlockingQueue` | - 크기가 무한하거나 고정된 블로킹 큐<br>- `ArrayBlockingQueue` 보다 더 효율적인 메모리 사용 |
102+
| `PriorityBlockingQueue` | - 우선순위 큐<br>- 우선순위가 높은 요소를 먼저 처리하는 블로킹 큐 |
103+
| `SynchronousQueue` | - 데이터를 저장하지 않는 블로킹 큐<br>- 생산자가 데이터를 추가하면 소비자가 그 데이터를 받을 때까지 대기 |
104+
| `DelayQueue` | - 지연된 요소를 처리하는 블로킹 큐<br>- 각 요소는 지정된 지연 시간이 지난 후에야 소비될 수 있음 |
105+
106+
<br>
107+
108+
## 동시성 컬렉션이 모든 상황에서 효율적일까?
109+
> No. 동시성 컬렉션이 모든 상황에서 최적화된 silver bullet은 아님
110+
111+
Thread Safe한 ArrayList 를 기준으로 비교해보자
112+
- SynchronizedList VS CopyOnWriteArrayList
113+
114+
<br>
115+
116+
#### 결론
117+
자료의 크기나 작업의 종류에 따라 가장 효율적인 컬렉션은 다룰 수 있음
118+
- `SynchronizedList`: 쓰기 작업이 읽기 작업보다 많은 경우
119+
- `CopyOnWriteList`: 읽기 작업이 쓰기 작업보다 많은 경우

0 commit comments

Comments
 (0)