개발/Java

메모리 가시성

jis1218 2024. 10. 20. 15:07
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myRunnable = new MyRunnable();

        Thread thread = new Thread(myRunnable);

        thread.start();
        Thread.sleep(1000);
        myRunnable.flag = false;
    }
    
    static class MyRunnable implements Runnable {

        boolean flag = true;

        @Override
        public void run() {

            System.out.println("실행 시작");
            while (flag) {
            }
            System.out.println("실행 종료");
        }
    }

위의 코드를 실행할 경우 실행 시작로그는 찍히지만 실행 종료로그는 찍히지 않는다. 그 이유는 main 스레드와 work 스레드가 참조하고 있는 flag 변수의 위치가 다르기 때문이다.

 |     CPU 1     |  |     CPU 2    |  
 |   _________   |  |   _________  |  
 |  | Level 1 |  |  |  | Level 1 | |  
 |  |   Cache |  |  |  |  Cache  | |  
 |  |         |  |  |  |         | |
 |  |_________|  |  |  |_________| |  
 |_______________|  |______________|
           | |              | |
           | |              | |
          _|_|______________|_|__
         |                       |
         |      MAIN MEMORY      | 
         |_______________________|

 

힙 영역이 있는 메인 메모리는 CPU 입장에서 거리가 멀다. CPU 입장에서는 빠르게 계산하기 위해 캐시 메모리를 참조하게 된다. CPU 대부분은 코어 단위에서 캐시 메모리를 가지고 있는데 main 스레드와 work 스레드가 참조하는 캐시 메모리의 위치가 다르기 때문에 main 스레드에서는 flag를 false로 바꿔 캐시 메모리의 flag가 false가 되었으나 work 스레드에서 참조하고 있는 flag는 여전히 true 이므로 실행 종료가 되지 않는다. 코어가 10개라면 1/10 확률로 실행 종료가 되지 않을까 싶다. 근데 10번 이상 실행해도 종료가 되지 않는다…

static class MyRunnable implements Runnable {

    volatile boolean flag = true;

    @Override
    public void run() {

        System.out.println("실행 시작");
        while (flag) {
        }
        System.out.println("실행 종료");
    }
}