|
| 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