> For the complete documentation index, see [llms.txt](https://kukingclass.gitbook.io/cleancode/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kukingclass.gitbook.io/cleancode/clean-code/13.md).

# 13장. 동시성

## 동시성이 필요한 이유

* 구조적 개선 동시성을 사용하면 what과 when을 분리하여 구조적으로 decoupling이 가능하다.
* throughput 개선 웹 크롤러: 단일 스레드 모델 대신 다중 스레드를 사용하면 더 빠르게 작업을 끝낼 수 있다. 대량 처리 시스템: 처리해야 하는 대량의 정보를 나누어 병렬로 처리
* 응답시간 개선 사용자와 상호작용 해야하는 시스템이라면 단일 스레드 모델의 경우 이전 사용자의 작업이 끝날 때 까지 다음 사용자는 작업이 불가능하다.

### 주의점

* 동시성이 항상 성능을 높여주지는 않는다. 여러 프로세서(CPU 코어) 가 동시에 처리할 수 있는 독립적인 작업이 충분히 많아야 한다.
* 동시성을 구현하려면 설계가 변해야 한다.
* 웹 프레임워크를 사용해도 동시성을 이해해야 한다. 동시성 원리를 모르고서는 리소스 동시 수정 제어(Lock), 데드락 등의 문제를 해결하기 힘들다.
* 동시성은 부하를 유발한다. 동시성을 위한 코드도 필요하고 생성한 스레드간의 컨텍스트 스위칭도 고려해야 한다.
* 동시성 버그는 재현하기 어렵다.

### 난관

```java
public class ThreadUnsafeClass {
    private int lastUsedId = 42;

    public int getNextId() {
        return ++lastUsedId;
    }
}
```

스레드 두 개가 위의 getNextId()를 부르면 어떻게 될까? 결과는 셋 중 하나다.

|       | 스레드 1 → getNextId() | 스레드 2 → getNextId() | lastUsedId | 결과 |
| ----- | ------------------- | ------------------- | ---------- | -- |
| 케이스 1 | = 43                | = 44                | = 44       | 정상 |
| 케이스 2 | = 44                | = 43                | = 44       | 정상 |
| 케이스 3 | = 43                | = 43                | = 43       | 오류 |

getNextId() 메서드가 한 줄짜리 메서드 처럼 보이지만 실제로 컴퓨터가 처리할 때는 여러 개의 바이트 코드 명령어로 나뉘어진다. 바이트 코드 명령을 실행하는 중간에 다른 스레드로 컨텍스트 스위치가 일어나는 경우 lastUsedId 값의 증가(++)가 아직 반영되지 않아서 세 번째 케이스와 같은 결과가 발생할 수 있다.

## 동시성 방어 원칙

### 단일 책임 원칙

> SRP는 주어진 클래스를 변경할 이유가 하나여야 한다는 원칙

동시성은 복잡성 하나만으로 기존 클래스에서 분리해야 한다. 즉 동시성 코드는 다른 코드와 분리해야 한다.

### 자료 범위 제한

임계영역은 동기화 영역으로 보호해야 한다. 보호해야 할 임계영역이 많으면 실수의 가능성이 커지기 때문에 임계영역의 수를 줄이는것이 중요하다.

### 데이터 사본 활용

공유할 자료를 처음부터 만들지 않는것이 가장 좋다. 필요한 객체를 복사본 DTO 등으로 감싸서 읽기 전용으로 사용하는 방법도 있다.

### 독립적인 스레드 구현

스레드간에 자료 공유를 하지 않도록 구현한다. 필요한 정보는 공유되지 않는 출처에서 가져오고 로컬 변수에 저장한다. 예를 들어 자바 서블릿 코드는 마치 독자적인 하나의 스레드에서 실행되는 것처럼 구현된다.

### Thread Safe 라이브러리 사용

*java.util.concurrent* 패키지를 활용한다. 예를 들어 다중 스레드 환경에서 Map을 사용해야 한다면 `ConcurrentHashMap` 구현체를 사용한다.

## 동기화 메서드간의 의존성을 이해하라

동기화 메서드간 의존성이 높으면 찾기 어려운 버그가 생길 가능성이 높다. 공유 클래스 하나에는 동기화 메서드 하나만 사용하는것을 권장한다.

## 동기화하는 부분을 작게 만들어라

동기화 영역은 한 번에 스레드 하나만 실행된다. 따라서 동기화 영역이 넓으면 다중 스레드의 장점을 살리기 어려워진다. 필요 이상으로 동기화 영역을 잡지 않았는지 확인한다.

### 올바른 종료 코드는 구현하기 어렵다

동시성 코드를 올바르게 종료하도록 만들기 쉽지 않다. 자식 스레드가 모두 종료되고 나서 부모 스레드가 종료되도록 하는 코드를 생각해 볼 수 있다. 예를 들어 자식 스레드가 Producer, Consumer 관계에 있는 경우 둘 중 한 스레드는 상대의 시그널을 기다리며 블락 되어 있을 수 있다. 이때 부모 스레드가 모든 자식 스레드에게 종료 명령을 내리면 한 스레드는 정상적으로 종료되었지만 다른 스레드는 블락되어있어 종료하지 못하는 데드락에 빠질 수 있다.

이렇듯 동시성 코드의 깔끔한 종료는 생각할 거리가 많으며 개발 초기부터 고민해야 한다.

### 스레드 코드 테스트하기

* 문제를 노출하는 TC를 작성하라
* 프로그램 설정과 부하를 바꾸어가며 자주 돌려라
* 말이 안되는 실패는 잠정적인 스레드 문제로 간주하라
  * 멀티 스레드 코드는 수백만 번에 한 번 발생하는 아주 드문 버그를 만들 수 있다. 때때로 실패하는 TC는 사실 멀티 스레드 버그라고 간주하는것이 안전하다.
* 멀티 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자
  * 스레드를 모르는 POJO부터 올바로 만들고 나서 멀티 스레드 코드를 테스트해라
* 멀티 스레드 코드를 다양한 환경에 쉽게 끼워 넣을 수 있게 구현해라
  * 스레드 수를 바꾸어서 실행한다.
  * 스레드 코드를 개발계, 테스트계에서 돌려본다. 운영 환경과 동일한 곳에서도 돌려본다.
  * TC를 다양한 속도로 돌려본다.
  * 반복 테스트가 가능하도록 TC를 작성한다.
* 상황에 맞게 스레드의 개수를 조절할 수 있도록 구현해라
* 프로세서 수 보다 많은 스레드를 돌려보라
  * 스레드간의 스와핑이 잦으면 멀티 스레드 코드에 숨은 버그 (놓친 임계영역 또는 데드락)를 찾기 용이하다.
* 다른 플랫폼에서 돌려보라
  * OS에 따라서 스레드 처리 정책이 다르기 때문에 원하는 결과가 나오지 않을 수 있다.
* 코드에 보조 코드를 넣어 돌려라. 강제로 실패를 일으키게 해보라

  * 스레드 코드의 오류를 찾기 어려운 이유는 코드가 실행되는 수많은 경로 중 소수의 경우에 실패하기 때문이다.

  * 드물게 발생하는 오류를 좀 더 자주 일으키게 하기 위해서 보조 코드를 추가해 스레드 코드의 실행 순서를 바꾸는 방법이 있다.

  * `Object.wait(), Object.sleep(), Object.yield(), Object.priority()` 등과 같은 메서드를 코드 중간 중간 삽입해 코드 실행 흐름을 흔들어 다양한 순서로 실행될 수 있도록 한다.

  > c.f. 코드에 보조 코드를 추가하기 위해서 직접 구현하는 방식과 AOF, CGLIB 등의 라이브러리를 사용해 자동화하는 방식이 있다.
