volatile

2024. 10. 1. 21:48BackEnd(Java)/Java

volatile 키워드는 자바에서 멀티스레드 환경에서 안전하게 값을 공유하기 위해 사용된다.

주로 변수의 값이 여러 스레드 간에 일관성 있게 공유되어야 할 때 사용되는데 volatile을 사용하면 변수의 값을 메인 메모리에서 직업 읽고 쓰도록 보장하여 스레드 간 가시성 문제를 해결한다.

 

volatile과 이중 검사 잠금 방식(Double Check Locking, DCL)

이중 검사 잠금 방식에서는 singletonClass 변수를 volatile로 선언하여 스레드 간에 객체의 초기화 상태를 일관성 있게 공유할 수 있다.

자바 메모리 모델에서 객체가 초기화되는 과정은 여러 단계로 이루어지며, volatile 없이 사용할 경우 객체의 완전한 초기화가 끝나지 않았음에도 다른 스레드가 그 객체를 참조하는 문제가 발생할 수 있게 된다.

 

다음 예시 코드를 보자

public class SingletonClass {

    /**
     * volatile 키워드를 사용하면 멀티스레딩을 쓰더라도 uniqueInstance 변수가 singleton 인스턴스로 초기화되는 과정이 올바르게 진행된다.
     */
    private volatile static SingletonClass uniqueInstance;

    private SingletonClass() {
    }

    //DCL(Double Checking Locking)을 사용하여 멀티스레딩 환경에서도 싱글턴이 돌아가도록 설정
    public static SingletonClass getInstance() {
        if (Objects.isNull(uniqueInstance)) {
            synchronized (SingletonClass.class) {
                if (Objects.isNull(uniqueInstance)) {
                    uniqueInstance = new SingletonClass();
                }
            }
        }

        return uniqueInstance;
    }
 }

 

 

왜 volatile이 중요한가?

  1. 메모리 가시성 문제 해결: 멀티스레드 환경에서 변수의 값이 CPU 캐시에 저장되어 다른 스레드들이 변경된 값을 보지 못하는 문제가 발생할 수 있다. volatile 키워드는 해당 변수가 항상 메인 메모리에서 읽고 쓰이도록 보장하여 이 문제를 해결한다.
  2. 인스턴스 초기화의 재정렬 방지: 객체가 생성되는 과정은 여러 단계로 이루어지며, 컴파일러나 JVM에 의해 코드 실행 순서가 최적화될 수 있어서 volatile 키워드를 사용하면 객체 초기화 과정에서 이런 재정렬이 방지되어, 다른 스레드에서 불완전하게 초기화된 객체를 참조하는 상황을 막을 수 있다

 

 

volatile 장점

  1. 메모리 가시성 보장
    volatile 키워드를 사용하면 변수의 값이 모든 스레드에서 즉시 반영됩니다. 변수의 값이 항상 메인 메모리에서 읽히기 때문에, 특정 스레드에서 변경된 값을 다른 스레드가 즉시 볼 수 있습니다. 이는 스레드 간에 데이터 불일치를 방지하는 중요한 역할을 합니다.
  2. 경량화된 동기화
    volatile은 synchronized 블록보다 비용이 적게 듭니다. synchronized는 블록에 들어가는 동안 잠금을 걸어야 하므로, 잠금을 관리하는 오버헤드가 있습니다. 반면, volatile은 메모리 가시성만 보장하므로 성능 면에서 더 효율적입니다.
  3. 재정렬 방지 (Reordering Prevention)
    자바 컴파일러는 성능 최적화를 위해 명령어의 재정렬을 할 수 있습니다. 하지만 volatile로 선언된 변수는 이런 재정렬을 방지하여, 스레드들이 변수의 상태를 올바르게 확인할 수 있게 합니다. 이는 특히 객체 초기화 단계에서 중요한 역할을 합니다.
  4. 안전한 읽기/쓰기
    volatile은 읽기 및 쓰기 작업 자체는 **원자성(atomic)**을 보장합니다. 즉, 다른 스레드에서 동시에 volatile 변수를 읽거나 쓰는 동안 데이터가 손상되지 않습니다.

volatile 단점

  1. 원자성(atomicity) 미보장
    volatile은 읽기/쓰기 자체는 원자적이지만, **복잡한 연산(예: 값 증가나 감소 등)**은 보장하지 않습니다. 예를 들어, volatile int 변수에 대해 i++와 같은 연산은 안전하지 않습니다. 이는 두 단계에 걸친 연산이기 때문에, 그 사이에 다른 스레드가 해당 값을 변경할 수 있습니다. 이런 경우에는 synchronizedAtomic 클래스(예: AtomicInteger)가 필요합니다.
  2. 잠금(lock) 미제공
    volatile은 단순히 변수의 값을 메모리에서 읽고 쓰는 작업만을 보장하므로, 복잡한 상태 변경이나 복수의 변수를 조작해야 하는 경우에는 동기화 메커니즘이 필요합니다. 예를 들어, 객체의 여러 필드를 변경하거나 비교 후 업데이트하는 작업 등에서는 synchronized 같은 잠금 메커니즘이 필요합니다.
  3. 복잡한 연산에 부적합
    volatile은 단일 변수의 상태가 즉시 반영되기를 원할 때 적합하지만, 여러 변수 간의 상호 관계를 유지해야 할 때는 적합하지 않습니다. 예를 들어, 트랜잭션과 같은 상황에서 여러 상태를 동기화해야 할 경우 volatile은 한계가 있습니다.
  4. 퍼포먼스 비용
    volatile을 사용하면 변수의 값을 항상 메인 메모리에서 읽고 써야 하기 때문에 메모리 접근 비용이 발생합니다. CPU 캐시를 사용할 수 없기 때문에, 빈번한 읽기/쓰기 작업에서 성능이 저하될 수 있습니다. 이로 인해 캐시 일관성 프로토콜에 따라 CPU 성능이 저하될 수 있습니다.
  5. 복잡한 객체 참조에는 한계
    volatile은 기본 자료형 또는 참조형 변수에 대해서는 안전하게 사용할 수 있지만, 복잡한 객체 내부의 필드까지는 보장하지 않습니다. 예를 들어, volatile로 선언된 객체의 내부 상태를 변경하는 경우, 내부 필드 변경은 동기화되지 않기 때문에 다른 스레드에서 불일치한 상태를 볼 수 있습니다.

 

volatile 장단점 요약

장점 단점
메모리 가시성 보장 원자성 보장 부족
경량화된 동기화 복잡한 연산에 부적합
명령어 재정렬 방지 잠금(lock) 기능 없음
안전한 읽기/쓰기 메모리 접근 비용 발생 가능
특정 케이스에서 성능 이점 복잡한 객체 참조 관리 어려움

 

volatile은 메모리 가시성을 보장하고, 간단한 동기화가 필요할 때 유용하지만, 복잡한 상태 관리가 필요한 경우에는 더 강력한 동기화 기법을 사용해야 합니다.

반응형

'BackEnd(Java) > Java' 카테고리의 다른 글

Equasls, HashCode에 대해서 알아보자  (0) 2022.12.06
Hash에 대해서 알아보자  (0) 2022.12.06
CompletableFuture  (0) 2022.11.22
추상클래스 vs 인터페이스  (0) 2022.09.01
call by value? call by reference?  (0) 2022.08.22