메모리 가시성

황상익·2024년 10월 4일

Inflearn JAVA

목록 보기
45/61

Volatile 메모리 가시성 1

public class VolatileFlagMain {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread t = new Thread(task, "work");
        log("runFlag = " + task.runFlag);
        t.start();

        sleep(1000);
        log("runFlag false 변경 시도");
        task.runFlag = false; //false 이기 때문에 task 종료?? -> 자바가 안끝남
        log("runFlag = " + task.runFlag);
        log("main 종료");
    }

    static class MyTask implements Runnable {
        //boolean runFlag = true;
        volatile boolean runFlag = true;

        @Override
        public void run() {
            log("task 시작");
            while (runFlag) {
                //runFlag  false 변경시 탈출
                //work Thread가 빠져나오지 못함
                //volatile 사용시 Memmory에서 변경 된 값 Thread가 바로 볼 수 있어야 한다.
            }
            log("task 종료");
        }
    }
}

runFlag을 사용해 스레드 작업 종료
boolean runFlag = true;을 사용했을때
main 스레드는 종료된 것을 확인 할 수 있지만, while 스레드는 종료되지 않을 것을 볼 수 있다.

실행되는 예상 결과는 main 스레드 종료시, task 스레드도 종료 될 것이라고 예상
하지만 main은 종료되었지만, task 스레드는 종료되지 않은 것을 확인 할 수 있음

메모리 가시성 문제가 발생하기 때문에 동시에 종료되지 않는다.
내 머리속 메모리 접근 방식

실제 메모리 접근 방식

여기서 핵심은 캐시 메모리에 runFlag 값만 반영, 메인 메모리에 이 값이 즉시 반영된다고 보기는 어렵다.
그렇다면 캐시 메모리의 runFlag의 값이 언제 메인 메모리에 반영??
알수 없다. 그렇다면 메인 메모리 또한 알 수 없다.

메모리 가시성

멀티 스레드 환경에서 한 스레드가 변경한 값이 다른 스레드에서 언제 보이는가?? 메모리 가시성

캐시 메모리를 사용하면 CPU 성능은 개선 가능, 하지만 성능 보다는 여러 스레드에서 같은 시점에 정확히 같은 데이터를 보는 것이 중요 할 수도 있다.

volatile boolean runFlag = true;

runFlag = false 변경되자마자 task 종료 되는 것을 확인 할 수 있다.

단 성능 이슈가 있기 때문에 필요한 곳에다가만 사용하는 것이 좋다.

public class VolatileCountMain {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        Thread t = new Thread(task, "work");
        t.start();

        sleep(1000);

        task.flag = false;
        log("flag = " + task.flag + " , count = " + task.count + " in main");
    }

    static class MyTask implements Runnable {
        //boolean flag = true;
        //long count;
        volatile boolean flag = true;
        volatile int count;

        @Override
        public void run() {
            log("task 시작");
            while (flag) {
                count++;
                //1억번에 한번씩 출려 5
                if (count % 1000_000_000 == 0) {
                    log("flag = " + flag + " , count = " + count + " in while");
                }
            }
            log("flag = " + flag + " , count = " + count + " 종료");
        }
    }
    //main과 work의 확인한 시점의 숫자가 다르다.
}

main 스레드가 flag를 변경하는 시점에 work 스레드도 flag 값을 변경 한다는 것을 정확히 알 수 있다.

Java Memory Model

Memory visibility

멀티스레드 환경엣 한 스레드가 변경한 값이 다른 스레드에서 언제 보이는지 대한 것을 메모리 가시성

Java Memory Model

멀티 스레드 프로그래밍에서 스레드 간 상호작용을 정의
여러 스레드들의 작업 순서를 보장하는 happens-before 관계

Happens-before

자바 메모리 모델에서 스레드 간의 순서 작업을 정의 A 작업에서의 모든 메모리 변경 사항은 B 작업에서 볼 수 있다. 즉, A 작업에서 변경된 내용은 B 작업이 시작되기 전에 모두 메모리에 반영된다.

Happens-before가 발생하는 경우

  1. 프로그램 순서 규칙
  2. volatile 변수 규칙
  3. 스레드 시작 규칙
  4. 스레드 종료 규칙
  5. interrupt 규칙
  6. 객체 생성 규칙
  7. 모니터 락 규칙
  8. 전이 규칙
profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글