From c221823ae9c7221efe01bd922de39f6ec652937a Mon Sep 17 00:00:00 2001
From: Heedong <59307414+ruthetum@users.noreply.github.com>
Date: Sun, 29 Sep 2024 14:34:10 +0900
Subject: [PATCH 1/2] week4: cas
---
heedong/README.md | 2 +-
heedong/cas.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+), 1 deletion(-)
create mode 100644 heedong/cas.md
diff --git a/heedong/README.md b/heedong/README.md
index 49c8294..b132dac 100644
--- a/heedong/README.md
+++ b/heedong/README.md
@@ -4,5 +4,5 @@
| 1주차 | • [프로세스와 스레드](./process-and-thread.md)
• [스레드 생성/실행](./thread-detail.md)(Thread, Runnable)
• [스레드 제어/생명주기](./thread-lifecycle.md) |
| 2주차 | • [volatile](./volatile.md)
• [synchronized](./synchronized.md)
• [concurrent.Lock](./concurrentLock.md) |
| 3주차 | • [생산 소비자 문제](bounded-buffer.md) |
-| 4주차 | |
+| 4주차 | • [CAS(Compare-And-Swap)](cas.md) |
| 5주차 | |
\ No newline at end of file
diff --git a/heedong/cas.md b/heedong/cas.md
new file mode 100644
index 0000000..30c2819
--- /dev/null
+++ b/heedong/cas.md
@@ -0,0 +1,74 @@
+# CAS - 동기화와 원자적 연산
+
+## 원자적 연산
+컴퓨터 과학에서 사용하는 **원자적 연산**(atomic operation)의 의미는 해당 연산이 더 이상 나눌 수 없는 단위로 수행된 다는 것을 의미
+
+> 예시와 함께 알아보자
+
+| 구분 | 구현 | 성능 | 원자성 보장 | 추가 설명 |
+|:---:|:-------------------------------------------:|:------------------:|:-------:|:-----------------------------------------------------------------:|
+| BasicInteger | int 형 타입 value
`synchronized` 미사용 | 가장 빠름 | X | 단일 스레드에서만 사용 가능 |
+| VolatileInteger | `volatile` int 형 타입 value
`synchronized` 미사용 | 느림 | X | 연산을 위한 안전한 임계 영역이 존재하지 않음 |
+| SyncInteger | int 형 타입 value
`synchronized` 사용 | 가장 느림 | O | 안전한 임계 영역 존재 |
+| MyAtomicInteger | `AtomicInteger` 사용 | synchronized 보다 빠름 | O | `synchronized` , `Lock(ReentrantLock)` 을 사용하는 경우보다 1.5 ~ 2배 정도 빠름 |
+
+락 기반(`synchronized`, `Lock`)의 경우 안전한 임계 영역이 존재하지 하지만, 값을 조회하고, 수정할 때 락 취득을 위한 코스트가 발생
+
+락 프리(`Atomic`) 기법은 CAS(Compare-And-Swap, Compare-And-Set) 연산 기반으로 락을 사용하지 않고 원자적 연산을 지원
+- CAS 연산은 락을 완전히 대체하는 것은 아니고, CPU 하드웨어 기반으로 **작은 단위의 일부 영역에 적용**
+
+
+
+## Atomic 클래스
+### 동작
+
+```mermaid
+---
+title: CAS 연산
+---
+flowchart
+ ai[AtomicInteger] --> atomic{원자적 연산인가?}
+ atomic -->|Yes| getAndSet["조회(get), 대입(set)"]
+ getAndSet --> volatile[volatile value]
+
+ atomic -->|No| compare["비교 연산(incr, compareAndSet)"]
+ compare --> compareAndOperate{"비교 연산 실행
(volatile 조회 후 연산)"}
+ compareAndOperate -->|"return true
(기댓값과 실제값이 같은 경우)"| success[저장]
+ success --> volatile
+ compareAndOperate -->|"return false
(기댓값과 실제값이 다른 경우)"| fail[처음부터 재시도]
+ fail --> compare
+```
+
+> CAS를 통해 스핀락(Spin Lock)을 구현하는 경우 `compareAndSet` 메서드 활용
+>
+> Spin Lock은 락을 획득할 때까지 계속 반복문을 돌면서 대기하기 때문에 락 취득을 위해 대기하는 시간이 길어지는 경우(e.g. CPU 연산이 오래 걸리는 경우) 성능 저하가 발생할 수 있음 (CPU 자원 계속해서 소모)
+
+스레드가 락을 획득하기 위해 대기하지 않기 때문에 대기 시간과 오버헤드가 줄어들기 때문에 성능 향상
+
+충돌(연산 시 기댓값과 실제값이 다른 경우)이 빈번하게 발생하는 환경에서는 성능에 문제가 될 수 있음
+- 여러 스레드가 자주 동시에 동일한 변수의 값을 변경하려고 시도할 때, CAS는 자주 실패하고 재시도해야 하므로 성능 저하가 발생할 수 있음
+- 이런 상황에서는 반복문을 계속 돌기 때문에 CPU 자원을 많이 소모하게 됨
+
+간단한 CPU 연산에는 락(Lock)보다는 CAS를 사용하는 것이 효과적
+
+
+
+### 비교
+
+| 구분 | 동기화 락(Lock) 방식 | CAS(Compare-And-Swap) 방식 |
+|:---:|:-----------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------|
+| 접근 방법 | 비관적(pessimistic) 접근법
(다른 스레드가 방해할 것이다) | 낙관적(optimistic) 접근법
(대부분의 경우 충돌이 없을 것이다) |
+| 설명 | 데이터에 접근하기 전에 항상 락을 획득
다른 스레드의 접근을 막음 | 락을 사용하지 않고 데이터에 바로 접근
충돌이 발생하면 그 때 재시도 |
+| 장점 | 충돌 관리: 하나의 스레드만 리소스에 접근할 수 있으므로 충돌 발생 X
안정성: 복잡한 상황에서도 일관성 있는 동작 보장
CPU 절약: 락을 대기하는 스레드는 CPU를 거의 사용하지 않음 | 낙관적 동기화: 락을 걸지 않고도 값을 안전하게 업데이트
충돌이 적은 경우 성능 향상: 락 프리(Lock-Free) 기반으로 락을 사용하지 않기 때문에 락을 획득하기 위해 대기하는 시간 X |
+| 단점 | 락 획득 대기 시간 증가: 스레드가 락을 획득하기 위해 대기해야 하므로 대기 시간이 길어질 수 있음
컨텍스트 스위칭 오버헤드: 락을 사용하면 락 획득을 대기하는 시점과 또 락을 획득하는 시점에 스레드의 상태가 변경되어 컨텍스트 스위칭이 발생할 수 있음 | 충돌이 빈번한 경우 성능 저하: 여러 스레드가 동시에 동일한 변수에 접근하여 업데이트를 시도할 때 충돌이 발생할 수 있음
스핀 락과 유사한 오버헤드: 충돌 시 반복적인 재시도를 하므로, 이 과정이 계속 반복되면 스핀 락과 유사한 성능 저하 발생 |
+
+
+
+### 요약
+일반적으로 동기화 락을 사용하고, 아주 특별한 경우에 한정해서 CAS를 사용해서 최적화해야 함
+- 빨리 끝나거나 단순한 연산(e.g. 카운트)에서는 CAS를 사용하는 것이 효과적
+- 오래 걸리거나 복잡한 연산(e.g. DB I/O 작업, Network I/O 작업)에서는 동기화 락 사용
+
+우리가 일반적으로 사용하는 많은 자바 동시성 라이브러리 및 동기화 컬렉션들은 성능 최적화를 위해 CAS 연산을 적극 활용하고 있음
+- 따라서 실무에서 직접 CAS 연산을 사용하는 사용하는 일은 매우 드뭄
+- 대신에 CAS 연산을 사용해서 최적화 되어 있는 라이브러리들을 이해하고 편리하게 사용할 줄 알면 충분함
\ No newline at end of file
From f20ecbdb5141cd9514c2a4ab666c8fbbef753d42 Mon Sep 17 00:00:00 2001
From: Heedong <59307414+ruthetum@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:28:37 +0900
Subject: [PATCH 2/2] =?UTF-8?q?week4:=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?=
=?UTF-8?q?=EC=BB=AC=EB=A0=89=EC=85=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
heedong/README.md | 2 +-
heedong/concurrent-collection.md | 119 +++++++++++++++++++++++++++++++
2 files changed, 120 insertions(+), 1 deletion(-)
create mode 100644 heedong/concurrent-collection.md
diff --git a/heedong/README.md b/heedong/README.md
index b132dac..c43acc4 100644
--- a/heedong/README.md
+++ b/heedong/README.md
@@ -4,5 +4,5 @@
| 1주차 | • [프로세스와 스레드](./process-and-thread.md)
• [스레드 생성/실행](./thread-detail.md)(Thread, Runnable)
• [스레드 제어/생명주기](./thread-lifecycle.md) |
| 2주차 | • [volatile](./volatile.md)
• [synchronized](./synchronized.md)
• [concurrent.Lock](./concurrentLock.md) |
| 3주차 | • [생산 소비자 문제](bounded-buffer.md) |
-| 4주차 | • [CAS(Compare-And-Swap)](cas.md) |
+| 4주차 | • [CAS(Compare-And-Swap)](cas.md)
• [동시성 컬렉션](concurrent-collection.md) |
| 5주차 | |
\ No newline at end of file
diff --git a/heedong/concurrent-collection.md b/heedong/concurrent-collection.md
new file mode 100644
index 0000000..e4f8b9f
--- /dev/null
+++ b/heedong/concurrent-collection.md
@@ -0,0 +1,119 @@
+# 동시성 컬렉션
+
+`java.util` 패키지에 소속되어 있는 컬렉션 프레임워크는 스레드 세이프(Thread Safe)하지 않음
+- Thread Safe: 여러 스레드가 동시에 접근해도 괜찮은 경우
+
+```java
+public class SimpleListMainV0 {
+
+ public static void main(String[] args) {
+ addAll(new BasicList());
+ }
+
+ private static void test(SimpleList list) throws InterruptedException {
+ log(list.getClass().getSimpleName());
+
+ // A를 리스트에 저장하는 코드
+ Runnable addA = new Runnable() {
+ @Override
+ public void run() {
+ list.add("A");
+ log("Thread-1: list.add(A)");
+ }
+ };
+
+ // B를 리스트에 저장하는 코드
+ Runnable addB = new Runnable() {
+ @Override
+ public void run() {
+ list.add("B");
+ log("Thread-2: list.add(B)");
+ }
+ };
+
+ Thread thread1 = new Thread(addA, "Thread-1");
+ Thread thread2 = new Thread(addB, "Thread-2");
+ thread1.start();
+ thread2.start();
+ thread1.join();
+ thread2.join();
+ log(list); // actual: [B, null]
+ }
+}
+```
+
+따라서 컬렉션에서도 여러 스레드에서 동시에 접근한다면 스레드 세이프한 컬렉션을 사용해야 함
+
+
+
+### 방법 1. 프록시 패턴 활용
+```java
+public static List synchronizedList(List list) {
+ return new SynchronizedRandomAccessList<>(list);
+}
+```
+
+컬렉션 내부의 모든 메서드를 `synchronized` 키워드를 추가하여 관리하는 것은 유지보수에 어려움이 있음
+
+따라서 프록시 패턴을 활용해서 컬렉션을 감싸는 방법을 사용 (e.g. `Collections.synchronizedList()`)
+- 내부의 모든 메서드에 `synchronized` 키워드가 추가
+
+하지만 단점도 존재함
+- 동기화 오버헤드 발생
+ - `synchronized` 키워드가 멀티스레드 환경에서 안전한 접근을 보장함
+ - 다만 각 메서드 호출 시마다 동기화 비용이 추가됨, 이로 인해 성능 저하가 발생할 수 있음
+- 전체 컬렉션에 대해 동기화가 이루어지기 때문에 잠금 범위가 넓어질 수 있음
+ - 이는 잠금 경합(lock contention)을 증가시키고, 병렬 처리의 효율성을 저하시키는 요인 유발
+ - 모든 메서드에 대해 동기화를 적용하다 보면 특정 스레드가 컬렉션을 사용하고 있을 때 다른 스레드들이 대기해야 하는 상황이 빈번해질 수 있음
+- 정교한 동기화가 불가능함
+ - `synchronized` 프록시를 사용하면 컬렉션 전체에 대한 동기화가 이루어지지만 특정 부분이나 메서드에 대해 선택적으로 동기화를 적용하는 것은 어려움
+ - 이는 과도한 동기화로 이어질 수 있음
+
+따라서 이 방식은 동기화에 대한 최적화가 이루어지지 않는 구현
+- 자바는 이런 단점을 보완하기 위해 `java.util.concurrent` 패키지에 동시성 컬렉션(concurrent collection)을 제공
+
+
+
+### 방법 2. 동시성 컬렉션 사용
+자바 1.5부터 `java.util.concurrent` 패키지에는 고성능 멀티스레드 환경을 지원하는 다양한 동시성 컬렉션 클래스들을 제공
+
+#### 컬렉션 인터페이스
+
+| 컬렉션 인터페이스 | 동시성 컬렉션 클래스 | 설명 |
+|:------------------:|:--------------:|:-----------------------:|
+| `List` | `CopyOnWriteArrayList` | `ArrayList`의 대안 |
+| `Set` | `CopyOnWriteArraySet` | `HashSet`의 대안 |
+| `Set` | `ConcurrentSkipListSet` | `TreeSet`의 대안 |
+| `Map` | `ConcurrentHashMap` | `HashMap`의 대안 |
+| `Map` | `ConcurrentSkipListMap` | `TreeMap`의 대안 |
+
+`LinkedHashSet` , `LinkedHashMap` 처럼 입력 순서를 유지하면서 멀티스레드 환경에서 사용할 수 있는 `Set` , `Map` 구현체는 제공하지 않음
+- 필요하다면 `Collections.synchronizedXxx()` 를 사용해야 함
+- 설계 철학, 성능 이슈(내부 추가 동기화) 및 코드 복잡성 등을 고려하여 제공하지 않음
+
+
+
+#### BlockingQueue 인터페이스
+
+ | 동시성 컬렉션 클래스 | 설명 |
+|:------------------:|:------------------------------------------------------------------------|
+| `ArrayBlockingQueue` | - 크기가 고정된 블로킹 큐
- 공정(fair) 모드를 사용할 수 있음, 다만 공정(fair) 모드를 사용 시 성능이 저하 |
+| `LinkedBlockingQueue` | - 크기가 무한하거나 고정된 블로킹 큐
- `ArrayBlockingQueue` 보다 더 효율적인 메모리 사용 |
+| `PriorityBlockingQueue` | - 우선순위 큐
- 우선순위가 높은 요소를 먼저 처리하는 블로킹 큐 |
+| `SynchronousQueue` | - 데이터를 저장하지 않는 블로킹 큐
- 생산자가 데이터를 추가하면 소비자가 그 데이터를 받을 때까지 대기 |
+| `DelayQueue` | - 지연된 요소를 처리하는 블로킹 큐
- 각 요소는 지정된 지연 시간이 지난 후에야 소비될 수 있음 |
+
+
+
+## 동시성 컬렉션이 모든 상황에서 효율적일까?
+> No. 동시성 컬렉션이 모든 상황에서 최적화된 silver bullet은 아님
+
+Thread Safe한 ArrayList 를 기준으로 비교해보자
+- SynchronizedList VS CopyOnWriteArrayList
+
+
+
+#### 결론
+자료의 크기나 작업의 종류에 따라 가장 효율적인 컬렉션은 다룰 수 있음
+- `SynchronizedList`: 쓰기 작업이 읽기 작업보다 많은 경우
+- `CopyOnWriteList`: 읽기 작업이 쓰기 작업보다 많은 경우