Effective Java ‐ Item 78⚠️ - woojin-playground/Backend-PlayGround GitHub Wiki
아이템 78 - 공유 중인 가변 데이터는 동기화해 사용하라.
동기화란?
- 프로세스나 스레드들이 서로 알고있는 공유 중인 데이터가 같은 것을 의미한다.
- 멀티 코어 프로세서의 힘을 제대로 활용하려면 멀티스레드 프로그래밍을 해야하며 여러 스레드가 동시에 접근하는 만큼 동기화에 주의를 기울여야 합니다.
- 동기화는 2가지 기능을 제공한다.
- 배타적 실행 : 현재 사용 중인 쓰레드만 접근이 가능하고 다른 쓰레드가 접근하지 못하게 한다.
- 쓰레드 사이 안정적인 통신 : 어떤 쓰레드가 변경한 데이터를 다른 쓰레드가 읽을 수 있게 한다.
동기화 방법 및 예시
synchronized
- 공유 변수 측에
synchronized 키워드가 붙으면 객체가 가진 고유 락으로 동시성 문제를 해결할 수 있다.
private int sum = 0;
@DisplayName("2개의 스레드로 1억을 만들기, synchronized을 이용해 Lock")
@Test
void twoThreadSumWithSynchronized() throws InterruptedException {
Thread thread1 = new Thread(this::workerThreadWithSynchronized);
Thread thread2 = new Thread(this::workerThreadWithSynchronized);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
assertThat(sum).isEqualTo(100_000_000);
}
private synchronized void workerThreadWithSynchronized() {
for (int i = 0; i < 25_000_000; i++) {
sum += 2;
}
}
volatile
- volatile 키워드는 각 쓰레드가 각 CPU Cache에 저장된 값이 다르기 때문에 읽는 변수의 값이 다른 문제를 해결해준다.
- CPU Cache에 반영되는 시점은 정확히 파악할 수 없기 때문에 volatile 키워드는 사용하면 Main Memory에 직접 접근할 수 있게 된다.
- 허나 이 방식은 정확도는 보장할 수 있으나 성능 측면에서는 좋지 않다.
Atomic
- atomic 패키지를 사용하면 정수 값들을 동기화시킬 수 있다.
- Atomic 패키지를 사용하면 CAS라는 알고리즘을 사용하는데 이 알고리즘은 메모리에 저장된 값과 CPU Cahce에 저장된 값들을 비교해 동일한 경우에만 update를 수행한다. 그래서
volatile키워드가 가지고 있던 단점을 커버하며 성능도 좋다.
private AtomicInteger atomicSum = new AtomicInteger();
@DisplayName("2개의 스레드로 1억을 만들기, AtomicInteger를 이용 (Lock이 아님)")
@Test
void twoThreadSumWithAtomic() throws InterruptedException {
Thread thread1 = new Thread(this::workerThreadWithAtomic);
Thread thread2 = new Thread(this::workerThreadWithAtomic);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
assertThat(atomicSum.get()).isEqualTo(100_000_000);
}
private void workerThreadWithAtomic() {
for (int i = 0; i < 25_000_000; i++) {
atomicSum.addAndGet(2);
}
}
요약
- 동기화는 두 가지를 제공한다.
- 현재 사용중인 스레드만 접근이 가능하고, 다른 스레드가 접근하지 못하게 한다.
- 어떤 스레드가 변경한 데이터를 다른 스레드에서 읽을 수 있게 한다.
- 여러 스레드가 가변 데이터를 공유한다면 그 데이터를 읽고 쓰는 동작은 반드시 동기화하자.
- 객체의 필드를 동기화하는데 많은 방법이 있으나 Lock을 최소화하기 위해 Atomic 패키지를 활용하자.