ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • java volatile
    개발 2026. 1. 8. 15:21

    아래와 같은 클래스가 있다고 해보자. 

    public static class SharedClass {
        private int x = 0;
        private int y = 0;
    
        public  void increment() {
            x++;
            y++;
        }
    
        public  void checkForDataRace() {
            if (y > x) {
                System.out.println("y > x - Data Race is detected");
            }
        }
    }

     

    x에 대한 증가가 y에 대한 증가보다 먼저 작성되었기 때문에 이론상으로 x>=y 인 상황만 발행해야하고 checkForDataRace 에서 if 문에 걸릴 일은 없다. 

     

    그런데 멀티 쓰레드 환경에서 실행해보면 y>x 인 케이스가 발생한다. 놀랍게도

    public class Main {
        public static void main(String[] args) {
            SharedClass sharedClass = new SharedClass();
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    sharedClass.increment();
                }
            });
    
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    sharedClass.checkForDataRace();
                }
    
            });
    
            thread1.start();
            thread2.start();
        }
     //
    
    y > x - Data Race is detected
    y > x - Data Race is detected
    y > x - Data Race is detected
    y > x - Data Race is detected

     

    컴파일러와 CPU는 성능 최적화를 위해서 종종 연산의 순서를 바꾸기도 한다. 논리적인 정확성을 유지하면서 순서를 바꾸는데 멀티 쓰레드환경에서는 규칙이 지켜지지 않는 문제가 발생한다. 

     

    위의 예제 코드에선 y++ 를 CPU가 먼저 실행하고 thread2 에서 이때 상태로 check를 걸었기 때문에 문제가 된다. 

     

    해결책은 두가지가 있다.

     

    첫번째는 SharedClass 의 함수에다가 synchronized 를 걸어주는 방법이다.  

    public static class SharedClass {
        private int x = 0;
        private int y = 0;
    
        public synchronized void increment() {
            x++;
            y++;
        }
    
        public synchronized void checkForDataRace() {
            if (y > x) {
                System.out.println("y > x - Data Race is detected");
            }
        }
    }

     

    직관적이긴 하지만 이 방법은 Core Grained Locking 이 되어서 성능이 덜어진다. 

     

    두번째는 volatile 을 사용하는 방법이다. volatile 로 선언된 변수에 대해선 실행의 재정렬을 막아주고 변수의 변경을 쓰레드가 즉시 볼 수 있게 해준다.

     

    위의 클래스에서 volatile 을 붙여주면 y>x 인 케이스가 발생하지 않게 된다

        public static class SharedClass {
            private volatile int x = 0;
            private volatile int y = 0;
    
            public void increment() {
                x++;
                y++;
            }
    
            public void checkForDataRace() {
                if (y > x) {
                    System.out.println("y > x - Data Race is detected");
                }
            }
        }

     

    단 연산의 원자성을 보장하진 않는다. 멀티쓰레드 환경에서 여러 쓰레드가 동시에 연산을 실행하면 결과 값이 예상과 달라질 수 있다. 

     

    그래서 Volatile 의 경우 쓰레드 하나만 값을 업데이트하고 나머지는 읽기 연산을 수행할 때 쓰는게 좋다. 

     

    이걸 Single Writer Multiple Reader 패턴이라고 Chat GPT 는 알려줬다. 

     

    개념 자체는 알겠는데 실전에서 쓰려면 기억이 안나서 실수가 잦는 키워드다.

     

    추가로 double, long 타입에 대한 reference operation 에서 원자성을 보장한다

     

    원래 자바에서는 int, boolean 같은 primitive 타입에선 특별한 동기화 작업을 걸지 않아도 원자성을 보장하는데 long, double 타입의 경우에는 예외였다. 

    private int getter() {
    	return intVal;
    }

     

    하지만 volatile 을 사용하면 reference 오퍼레이션에 대해서도 원자성을 보장한다

     

    public static class Metrics {
        private long count = 0;
        private volatile double average = 0.0; 
    
        public synchronized void addSample(long sample) {
    		// 여기에선 volatile 로 선언 안해도 괜춘함
    		// synchronized 에서 쓰레드가 하나씩만 접근하게 되므로
            double currentSum = average * count; 
            count++;
            average = (currentSum + sample) / count;
        }
    
        public double getAverage() {
            // average 변수를 volatile 로 선언해줘야
            // reference 가 atomic operation 임이 보장된다
    		// double, long 타입은 volatile 을 반드시 붙여줘야함
            return average;
        }
    }

    '개발' 카테고리의 다른 글

    Python GIL  (0) 2025.12.28
    [Kube] Node, Node Pool, Taint, Label  (1) 2024.11.27
    [Python] Decorator  (0) 2024.05.17
    [파이썬] 파이썬은 모든것이 객체다  (0) 2024.05.09
    [python] __all__ 을 이용해 전체 임포트 막기  (0) 2024.05.08

    댓글

Designed by Tistory.