Effective Java를 읽고 정리한 정리본입니다.

📌 Item 78 : 공유 중인 가변 데이터는 동기화해 사용하라

🫧 synchronized

  • 해당 메서드나 블록을 한 번에 한 스레드씩 수행하도록 보장한다.
  • 확인 시 최신 버전의 값을 반환해준다.

특히 두 번째 특징이 중요하다.

같은 락의 보호하에 수행된 모든 이전 수정의 최종 결과를 보게 해 주는 두 번째 특징이 없다면 제대로 된 동기화가 이루어지지 않을 확률이 발생한다.

그 이유를 아래에서 살펴보자.

🫧 동기화

동기화를 말하려면 우선 두 가지 조건이 충족되어야 한다.

  1. 배타적 수행 (원자성)
  2. 스레드 간 통신

✨ 원자성

자바에서 언어 명세상 long과 double 외의 변수를 읽고 쓰는 동작은 원자적이다.

즉, 여러 스레드가 같은 변수를 동기화 없이 수정하는 중이라 해도 항상 어떤 스레드가 정상적으로 저장한 값을 온전히 읽어옴을 보장한다.

또한, 이를 위해서는 쓰기 메서드와 읽기 메서드 모두 동기화 해야 함을 기억하자!

✨ 스레드 간 통신

자바 언어 명세는 스레드가 필드를 읽을 때 항상 ‘수정이 완전히 반영된’ 값을 얻는다고 보장하지만, 한 스레드가 저장한 값이 다른 스레드에게 ‘보이는가’는 보장하지 않는다.

이는 스레드 간 통신 조건이 충족되지 않았음을 의미하는데, 이를 해결하기 위해서는 다음과 같은 방법을 사용할 수 있다.

  1. volatile 한정자
  2. synchronized 키워드

✨ volatile

  • 동기화 속도가 빠르다
  • 배타적 수행과 상관 없이 항상 가장 최근에 기록된 값을 읽게 됨을 보장한다

✨ volatile의 문제

volatile은 속도가 빠르고, 최근 기록 값 반환을 보장하지만 배타적 수행을 보장하지 않는다는 문제가 존재한다.

특히 i++과 같은 상황에서 발생하는데, i의 값을 읽고 1 증가한 값을 저장하는 과정에서 i가 다른 값으로 바뀐다면 이를 모르는 기존 스레드는 기존 불러온 값인 i+1을 그대로 덮어씌울 것이다.

이런 오류를 안전 실패라 하며, 안전 실패 문제는 다행히도 synchronized 키워드를 붙임으로써 해결 가능하다.

✨ AtomicLong

  • 락 없이도 스레드 안전한 프로그래밍을 지원하는 클래스들을 담은 패키지
  • 원자성 + 통신을 모두 지원한다