Nick Dev

[JAVA] volatile ⁉ 본문

Java

[JAVA] volatile ⁉

Nick99 2024. 12. 11. 12:50
반응형

volatile 키워드 의미

  • 해당 변수를 main memory에 저장하고 읽어오겠다고 명시하는 키워드
  • volatile 키워드로 선언한 변수는 모든 읽고 쓰기를 cpu register(cpu cache)에 하지 않고 바로바로 main memory에 하겠다!!!
    • volatile 변수 값 읽을 때, CPU cache가 아닌 main memory에서 읽어옴
    • volatile 변수 값 쓸 때, CPU cache가 아닌 main memory에 씀

기본적인 thread의 처리 방식

  • thread가 작업을 시작하게 되면 main memory에서 해당 작업에 필요한 변수들을 자신의 CPU cahce에 복사해놓고 쓴다
  • thread가 작업하고 있는 시점을 보면, main memory에서 가져온 변수가 cpu cache에 저장된 값과 main memory에 저장되어 있는 값이 다를 수 있다

✅ volatile 키워드 변수가 필요한 이유!!!

  • 현대 컴퓨터들은 멀티 코어이기에, 각 thread들은 서로 다른 CPU에서 동시에 실행되고 있다.
  • thread 1에서 변수를 수정해도 이 수정된 값이 언제 main memory로 반영되고, thread 2는 언제 main memory에서 가져올지 시기를 알지 못한다.
  • 즉, thread 1에서 수정한 변수가 아직 main memory에 반영되지 않았기에 다른 thread들에서는 해당 변수에 대한 최신 값을 보지 못한다 = 가시성 문제 발생!!
  • volatile 변수는 읽고 쓰기를 main memory에 바로 하기 때문에, 특정 thread가 값을 쓰자마다 다른 thread들은 최신 값을 바로 볼 수 있다

volatile의 특징

가시성 보장

  • 기본적으로 non-volatile 변수(일반적으로 우리가 아는 그런 변수)는 멀티 스레딩 환경에서 가시성이 보장되지 않는다

Full volatile visibility guarantee

  • 완전한 휘발성 가시성 보장
public class MyClass {
    private int years;
    private int months
    private volatile int days;


    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }

    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

}
  • update() 메서드에서 days가 수정될 때, 앞서 수정한 yearsmonths의 값도 같이 바로 main memory에 반영한다.
  • totalDays() 메서드에서 total += years * 365;volatile 변수 읽을 때, main memory에서 읽어 온다

volatile 변수는 바로 main memory에 반영되는건 알겠는데, yearsmonthsvolatile 변수도 아닌데 왜 같이 main memory에 반영되는거야????

  • 이게 바로 happens-before-gurantee 라는 것!

✅ happens-before-gurantee 이해를 위한 배경 지식

  • 기본적으로 JVM은 성능을 향상하기 위해 sementic이 바뀌지 않는 선에서 알아서 instruction의 순서를 재정렬한다 -> 이걸 ** instruction reordering**이라고 함
  • 근데 위와 같은 상황에서 days만 최신 데이터로 main memory에 업데이트하고, 나머지 years, months는 최신으로 업데이트하지 않으면 데이터 일관성이 유지되지 않는다.
    • 왜냐면 years, months, days는 서로 연관되어 있는 데이터이기에 하나의 변수만 가시성을 유지하는게 좀 이치에 맞지 않다.
    • days만 최신 데이터나머지는 최신 데이터가 아니라면 이게 무슨 의미가 있는가...
  • 그럼 변수 3개 다 volatile 키워드로 선언하면 되는거 아닌가? 라는 생각이 드는 건 당연하다
  • 근데 뭔가 모든 변수를 다 volatile로 선언해야만 될 것 같은 안좋은 기분이 든다..
  • 그래서 Java에서는 Happens-Before을 보장함으로써, 이를 어느정도 해결했다!!

✅ happens-before-gurantee 특징!!

  • volatile 변수에 write하기 전의 모든 작업은 해당 write 작업 이후로 재정렬되지 않는다.
    • 즉, volatile 변수 write 기준으로 (여기선 this.days = days; 이 부분) 이전의 read, write무조건 volatile 변수 write 이전에 발생한다
    • this.years = years;, this.months = months; 얘네들이 JVM의 instruction reordering으로 인해 this.days = days; 이 instruction 뒤로 갈 일은 절대로 네버 일어나지 않는다!!!
    • 하지만 volatile 변수 write 이후의 read/write는 해당 write 이전으로 재정렬될 수 있다
  • volatile 변수를 read 후의 read/write는 작업은 재정렬되어 해당 read 작업 이전으로 재정렬될 수 없다.
    • volatile 변수 read 이전의 read 작업은 해당 read 이후로 재정렬될 수 있습니다.

이쯤 되면 또 의문이 들 수 있다.. 만약 volatile 변수에 대해서 여러 thread가 동시에 write를 하게 되어도 가시성이 보장될까??

  • 우선 가시성은 동시성이랑 다르다!!!
  • 가시성을 보장한다는 말은 한 thread에서 수행된 변경사항을 다른 thread에서 즉각 보이는 것을 보장하는 것
  • 동시성을 보장한다는 말은 여러 thread가 한 리소스(여기선 volatile 변수)에 접근했을 때, race condition이 발생되지 않도록 보장해주는 것
  • 정리하면, 여러 thread가 동시에 write를 진행해도 가시성은 보장되지만 동시성은 보장되지 않는다!!
    • 여러 thread들 중에 어쨋든 한 thread의 값이 결국 main memory에 저장될 것임 -> 이 값을 모든 thread에서 바로 볼 수 있기에 결국 가시성은 보장됨
    • 하지만 여러 thread의 변경사항 중 결국 어떤 것이 저장될지는 모르기에 동시성은 보장되지 않음

가시성 보장한다는게 자칫 동시성도 보장해주는 것처럼 느껴지지만 절대 아니다!!!

그럼 언제 volatile 키워드를 사용할까??

  • 두 thread가 공유 변수에 대해 읽고 쓰는 경우 volatile로 해결 X → synchronized 처리 해줘야 함
  • 한 개의 thread만 volatitle 변수를 읽고 쓰고, 나머지 thread들은 읽기만 하면 가시성 보장 가능

예시 1 - 다른 thread 실행을 제어할 때

public class TaskRunner {
    private volatile boolean running = true;

    public void start() {
        new Thread(() -> {
            while (running) {
                // 반복 작업 수행
            }
        }).start();
    }

    public void stop() {
        running = false; // 다른 스레드에서 접근하여 상태 변경
    }
}

예시 2 - 가끔 변경되지만 자주 읽힐 때

public class ConfigCache {
    private volatile Config config;

    public void updateConfig(Config newConfig) {
        config = newConfig;
    }

    public Config getConfig()
}

발생 가능한 성능 이슈

  • 변수의 read&write를 CPU L1 캐시가 아닌 Main Memory에 하기 때문에 비용이 더 발생한다
  • volatile 변수 때문에 성능을 위한 instruction reordering을 못함
  • 동기화 처리를 해줘야 되기에 성능 하락 가능
반응형

'Java' 카테고리의 다른 글

[JAVA] About 제네릭  (0) 2024.12.29
[JAVA] CAS 알고리즘이란?!  (0) 2024.12.11
[JAVA] Thread vs Process  (0) 2024.12.11
[JAVA] 객체 지향의 4대 특성 - 캡!상추다  (0) 2024.12.11
[JAVA] HashMap의 내부 구현 방식  (0) 2024.12.11