[아이템78] 공유 중인 가변 데이터는 동기화해 사용하라

2024. 12. 11. 23:53·1️⃣ 백앤드/이펙티브 자바

1. 동기화

synchronized 키워드를 통해 메서드나 블록에 한 쓰레드씩 접근할 수 있도록 할 수 있다. 즉, 한 쓰레드가 변경중인 상태가 일관되지 않은 순간의 객체를 다른 쓰레드가 접근하지 못하는 용도로 사용한다.

synchronized 키워드

일종의 락을 걸고, 객체의 상태를 변화시킨다. 동기화를 제대로 사용한다면 어떤 메서드도 이 객체의 상태가 일관되지 않은 순간을 볼 수 없을 것이다. 이를 동기화라고 한다. 그리고, 동기화 된 메서드나 블록에 접근해 수행한 쓰레드가 만든 변화는 다른 쓰레드에서 확인이 가능하다.

long, double 외 변수를 읽고 쓰는 동작은 원자적이다. 여러 쓰레드가 같은 변수를 동기화 없이 수정해도, 항상 어떤 쓰레드가 정상적으로 저장한 값을 온전히 읽어온다. 그렇다고 이 경우에 동기화를 배척해서는 안된다.

그러면, 동기화가 안된 상황을 가정해보자. 동기화에 실패했을 때 여러 쓰레드가 가변 데이터를 읽고 쓴다면 어떻게 될까?

1-1. 동기화가 안된 예시

public class StopThread {
    private static boolean stopRequested;
    
    public static void main(String[] args) throws InterruptedException {
    	Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested) {
            	i++;
            }
        });
        backgroundThread.start();
        
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

첫 번째 쓰레드가 stopRequested 필드를 보고, 다른 쓰레드에서 true 로 바꿔주지 않는다면 무한으로 돌게되는 예제이다. 그렇다면, 1초 후에 종료될까? 메인쓰레드가 값을 true 로 바꿔주었는데, 과연 혼자 열심히 돌고있는 backgroundThread 는 멈추었을까? 결과는 영원히 수행되었다. 동기화해주지 않았기 때문에, 메인 쓰레드가 수정한 값을 백그라운드 쓰레드가 알아채지 못한다. 그나마 JVM 이 아래와 같이 최적화를 해준다.

if (!stopRequested)  // 끌어올리기(최적화 기능) : 응답 불가 상태가 됌
    while (true)
    	i++;

1-2. 동기화 한 예시

public class StopThread {
    private static boolean stopRequested;

    private static synchronized void requestStop() {
        stopRequested = true;
    }

    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested())
                i++;
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

stopRequested 필드를 동기화해 접근하도록 requestStop() - 쓰기 메서드, stopRequest() - 읽기 메서드를 만들어 사용하면, 기대대로 1초 후에 종료된다. 반드시 쓰기와 읽기 모두 동기화해야 동작을 보장한다.

1-3. 동기화 대안

public class stopThread {
    private static volatile boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested)
                i++;
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

stopRequested 필드를 volatile 으로 선언하면 동기화를 생략해도 된다. volatile 항상 가장 최근에 기록된 값을 읽게 됨을 보장한다. 하지만, volatile 한정자는 주의해서 사용해야 한다. 아래 예시를 살펴보자.

private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
    return nextSerialNumber++;
}

일련번호를 생성할 의도로 작성한 메서드이다. 매번 고유한 값을 반환할 의도로 만들어 진 메서드에도 동기화 없이는 올바르게 작동하지 않는다. 문제는 증가 연산자(++)이다. 값을 읽고, 1 증가하여 저장한다. 만약 두 번째 쓰레드가 이 사이를 비집고 들어와 값을 읽어가면, 첫 번째 쓰레드와 똑같은 값을 돌려받게 된다. synchronized 를 붙이고 volatile 한정자를 제거하자. 한편, java.util.concurrent.atomic 패키지의 AtomicLong 을 사용할 수도 있다.

private static final AtomicLong nextSerialNum = new AtomicLong();

public static long generateSerialNumber() {
    return nextSerialNum.getAndIncrement();
}

락 없이도 스레드 안전한 프로그래밍을 지원한다. 성능도 우수하다.

 

2. 정리

최우선은, 가변 데이터를 공유하지 말자. 불변 데이터만 공유하거나, 아무것도 공유하지 말자. 가변 데이터는 단일 스레드에서만 사용하자. 만약 여러 스레드가 가변 데이터를 공유한다면, 그 데이터를 읽고 쓰는 동작은 반드시 동기화 해야한다.

 

'1️⃣ 백앤드 > 이펙티브 자바' 카테고리의 다른 글

[아이템80] 스레드보다는 실행자, 태스크, 스트림을 애용하라  (0) 2024.12.16
[아이템79] 과도한 동기화는 피하라  (0) 2024.12.12
[아이템77] 예외를 무시하지 말라  (0) 2024.12.11
[아이템76] 가능한 한 실패 원자적으로 만들라  (0) 2024.12.10
[아이템75] 예외의 상세 메시지에 실패 관련 정보를 담으라  (1) 2024.12.10
'1️⃣ 백앤드/이펙티브 자바' 카테고리의 다른 글
  • [아이템80] 스레드보다는 실행자, 태스크, 스트림을 애용하라
  • [아이템79] 과도한 동기화는 피하라
  • [아이템77] 예외를 무시하지 말라
  • [아이템76] 가능한 한 실패 원자적으로 만들라
HOZINU
HOZINU
주니어 백앤드 개발자의 세상만사 이모저모. 주로 개발 이야기를 다룸.
  • HOZINU
    백엔드 탐험 일지
    HOZINU
  • 전체
    오늘
    어제
  • 블로그 메뉴

    • ⛪ HOME
    • 🌍 GITHUB
    • 카테고리 (73)
      • 1️⃣ 백앤드 (72)
        • 이펙티브 자바 (72)
      • 2️⃣ CS (0)
        • 운영체제 (0)
        • 네트워크 기초 (0)
        • 네트워크 응용 (0)
        • SSL & PKI (0)
        • 기타 (0)
      • 3️⃣ 코딩테스트 (0)
      • 4️⃣ 개인공부 (0)
        • MSA (0)
        • REDIS (0)
      • 5️⃣ 일상이야기 (1)
  • 인기 글

  • 태그

    싱글턴
    정보은닉
    try-with-resources
    빌더
    캡슐화
    표준예외
    finalizer
    Comparable
    Cleaner
    equals
    맥북
    계층구조
    CLONE
    멤버클래스
    컴포지션
    의존객체
    로타입
    hashcode
    정적 팩터리 메서드
    optional
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
HOZINU
[아이템78] 공유 중인 가변 데이터는 동기화해 사용하라
상단으로

티스토리툴바