IT/JAVA

Java 멀티쓰레드 프로그래밍: 메모리 모델과 가시성 문제 해결

KeepGooing 2024. 11. 27. 11:23
반응형

Java 메모리 모델(JMM)을 이해하고 멀티쓰레드 환경에서의 가시성 문제를 해결하는 것은 안정적인 동시성 프로그래밍의 핵심입니다. 이 글에서는 JMM의 심층적인 이해와 가시성 문제 해결 기법을 탐구합니다.

1. Java 메모리 모델 심층 분석

JMM은 멀티쓰레드 프로그램의 동작을 정의합니다. 주요 개념을 살펴봅시다.

public class JMMExample {
    private int x = 0;
    private int y = 0;
    private volatile boolean flag = false;

    public void writer() {
        x = 1;  // 1
        y = 2;  // 2
        flag = true;  // 3: volatile 쓰기
    }

    public void reader() {
        if (flag) {  // 4: volatile 읽기
            System.out.println(x + y);  // 5
        }
    }
}
        

이 예제에서 volatile 키워드는 메모리 장벽(memory barrier)을 생성하여 가시성을 보장합니다.

2. 가시성 문제 해결 기법

다양한 방법으로 가시성 문제를 해결할 수 있습니다.

public class VisibilitySolutions {
    // 1. volatile 키워드 사용
    private volatile boolean flag = false;

    // 2. AtomicReference 활용
    private AtomicReference atomicValue = new AtomicReference<>(0);

    // 3. synchronized 블록 사용
    private int syncValue = 0;
    public synchronized void updateSyncValue(int newValue) {
        syncValue = newValue;
    }

    // 4. Lock 인터페이스 활용
    private final Lock lock = new ReentrantLock();
    private int lockProtectedValue = 0;
    public void updateWithLock(int newValue) {
        lock.lock();
        try {
            lockProtectedValue = newValue;
        } finally {
            lock.unlock();
        }
    }

    // 5. ThreadLocal 사용
    private ThreadLocal threadLocalValue = ThreadLocal.withInitial(() -> 0);
    public void updateThreadLocal(int newValue) {
        threadLocalValue.set(newValue);
    }
}
        

3. Happens-Before 관계 이해하기

Happens-Before 관계는 JMM에서 메모리 가시성을 보장하는 핵심 개념입니다.

public class HappensBefore {
    private int value = 0;
    private volatile boolean flag = false;

    public void writer() {
        value = 42;  // 1
        flag = true;  // 2
    }

    public void reader() {
        if (flag) {  // 3
            System.out.println(value);  // 4
        }
    }
}
        

이 예제에서 1 happens before 2, 3 happens before 4, 그리고 2 happens before 3이 보장됩니다.

*실행 우선 순위 1>2>3>4    

4. 사용자 정의 메모리 장벽 구현

때로는 사용자 정의 메모리 장벽이 필요할 수 있습니다. Unsafe 클래스를 사용한 예제입니다.

public class CustomMemoryBarrier {
    private static final Unsafe unsafe;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void fullFence() {
        unsafe.fullFence();
    }

    public static void main(String[] args) {
        int x = 0;
        x = 1;  // 일반 쓰기
        fullFence();  // 메모리 장벽
        // 이후의 모든 읽기 연산은 위의 쓰기가 완료된 후에 실행됨을 보장
    }
}
        

결론

Java 메모리 모델과 가시성 문제를 깊이 이해하고 적절한 해결 기법을 적용하는 것은 안정적인 멀티쓰레드 프로그래밍의 기초입니다. 다양한 기법을 상황에 맞게 활용하여 동시성 문제를 효과적으로 해결할 수 있습니다.

메모리 모델과 가시성은 복잡한 주제이지만, 이를 마스터하면 고성능 멀티쓰레드 애플리케이션 개발에 큰 도움이 됩니다. 지속적인 학습과 실험을 통해 이 개념들을 완벽히 이해하세요.

반응형