Light Blue Pointer
본문 바로가기
Developing/TIL(Develop)

Java volatile / synchronized

by Greedy 2024. 5. 31.

Java에서 volatile과 synchronized는 멀티스레딩 환경에서 안전하게 데이터를 접근하고 수정하기 위한 동시성 제어 키워드이다.

이 두 키워드는 각각 다른 방식으로 동기화를 제공한다.

volatile

volatile 키워드는 변수의 값을 모든 스레드가 항상 최신 상태로 읽을 수 있도록 보장한다.

자바 메모리 모델에서 각 스레드는 자신의 캐시를 사용하여 변수를 읽고 쓸 수 있다.

volatile로 선언된 변수는 각 스레드의 캐시에 저장되지 않고 항상 주 메모리에서 읽고 쓰기 때문에 여러 스레드가 동시에 접근하더라도 일관된 값을 보장한다.

주요 특징

변수의 가시성 보장

volatile로 선언된 변수는 각 스레드가 해당 변수의 최신 값을 볼 수 있도록 한다.

원자성 보장 불가

volatile은 변수의 읽기/쓰기의 원자성을 보장하지 않는다.

복합 연산(예: i++)에 대해 안전하지 않다.

예제

public class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (!flag) {
                // flag가 true가 될 때까지 대기
            }
            System.out.println("Flag has been set to true!");
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(1000); // 1초 대기
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            flag = true; // flag 값을 true로 설정
            System.out.println("Flag set to true.");
        }).start();
    }
}

synchronized

synchronized 키워드는 한 번에 하나의 스레드만 특정 블록이나 메서드에 접근할 수 있도록 하는 방법을 제공한다.

이는 코드의 동시성을 제어하여 여러 스레드가 동시에 같은 자원에 접근할 때 발생할 수 있는 문제를 예방한다.

주요 특징

상호 배제 (Mutual Exclusion)

특정 블록이나 메서드가 한 스레드에 의해 사용되고 있을 때 다른 스레드는 해당 블록이나 메서드에 접근할 수 없다

가시성 보장

synchronized 블록 안의 모든 변수는 해당 블록을 나가는 시점에 메인 메모리에 쓰여지고, 다른 스레드는 해당 블록에 들어올 때 메인 메모리에서 최신 값을 읽는다.

예제

public class SynchronizedExample {
    private int count = 0;

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();
        example.doWork();
    }

    public synchronized void increment() {
        count++;
    }

    public void doWork() {
        Thread thread1 = new Thread(this::increment);
        Thread thread2 = new Thread(this::increment);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("Count: " + count);
    }
}

volatile vs synchronized

특징 volatile synchronized

가시성 모든 스레드가 항상 최신 값을 읽을 수 있도록 보장 블록이나 메서드 내의 변수 값이 최신 상태로 유지되도록 보장
원자성 단순 읽기 및 쓰기 연산만 원자성 보장 모든 연산의 원자성 보장
사용 사례 단순한 플래그나 상태 값 변경 복합 연산이나 공유 자원 보호
성능 비용이 적음 비용이 높음 (잠금과 잠금 해제)
사용법 변수에 직접 적용 메서드나 블록에 적용

volatile

변수가 변경될 때마다 모든 스레드가 즉시 최신 값을 볼 수 있도록 한다

간단한 읽기/쓰기 작업에 적합하다

복합 연산에는 적합하지 않다 (예: i++)

synchronized

블록 또는 메서드에 대해 상호 배제를 제공한다

원자성을 보장한다

가시성을 보장한다

성능 오버헤드가 있을 수 있다

따라서, 두 키워드는 각각의 용도에 맞게 사용되어야 하며, 특정 상황에 맞는 동기화 메커니즘을 선택하는 것이 중요하다.

volatile은 간단한 변수의 가시성 보장에 사용하고, synchronized는 더 복잡한 동기화가 필요한 경우에 사용한다