Skip to content

Dev

Java - volatile

volatile1 min read

1. volatile와 가시성

volatile은 멀티쓰레드 환경에서 메모리 가시성 확보를 위해 사용한다.

멀티쓰레드 환경에서는 메모리 가시성 문제가 발생하는데, 변경 가능한 공유데이터가 쓰레드마다 다른 값을 취하게 될 수 있기 때문이다.

아래는 이펙티브 자바에서 동시성과 관련된 예제 중 하나이다.

1private static boolean stopRequested;
2
3public static void main(String[] args) {
4 Thread backgroundThread = new Thread(new Runnable() {
5 public void run() {
6 int i=0;
7 while(!stopRequested){
8 i++;
9 }
10 }
11 });
12 backgroundThread.start();
13
14 TimeUnit.SECONDS.sleep(1);
15 stopRequested = true;
16}

main 쓰레드가 공유 데이터인 stopRequested 변수를 1초뒤 true로 수정해서 backgroundThread의 while문 조건이 만족되지 않아, 1초뒤 종료될 것 같지만 해당 프로그램은 종료가 되지 않는다. 여기서 발생되는 문제가 메모리 가시성 문제이다. volatile은 이런 메모리 가시성 문제를 해결해준다.

2. 메모리 구조

운영체제가 쓰레드 스케줄링을 통하여 코어에 할당하고 실행하게 된다. 코어에서 CPU의 초당 수많은 명령어를 수행할 수 있기 때문에 성능향상을 목적으로 RAM에서 데이터를 가져오는 것이 아닌 코어 내부의 캐시영역에서 데이터를 가져오게 된다.

1+---- cpu core ---+ +---- cpu core ---+
2| Main Thread | | backgroundThread|
3| +-------------+ | | +-------------+ |
4| | cpu cache | | | | cpu cache | |
5| | (false) | | | (false) | |
6| +-------------+ | | +-------------+ |
7+-----------------+ +-----------------+
8 ▲ ▲
9 | |
10 | |
11+----------------------------------+
12| RAM |
13| (stopRequested : false) |
14+----------------------------------+

어플리케이션이 실행되면서 메인메모리로부터 초기화된 stopRequested의 false값을 각 쓰레드마다 독립적인 공간인 CPU캐시 영역에 저장하게 된다. 이후 각 쓰레드별고 데이터를 읽거나 쓸때 thread별 독립적인 공간인 cpu cache에서 데이터를 처리하기 때문에 메모리 가시성 문제가 발생하게 된다.

1+---- cpu core ---+ +---- cpu core ---+
2| Main Thread | | backgroundThread|
3| | ||
4|true | | false |
5| +-------------+ | | +-------------+ |
6| | cpu cache | | | | cpu cache | |
7| | (true) | | | | (false) | |
8| +-------------+ | | +-------------+ |
9+-----------------+ +-----------------+
10 ▲ ▲
11 | |
12 | |
13+----------------------------------+
14| RAM |
15| (stopRequested : false) |
16+----------------------------------+
3. volatile를 통한 가시성 확보

volatile 키워드를 사용하면 메인메모리로부터 직접 접근하고 쓸 수 있기 때문에 데이터에 대한 가시성을 보장한다.

1private volatile static boolean stopRequested;
2
3public static void main(String[] args) {
4 Thread backgroundThread = new Thread(new Runnable() {
5 public void run() {
6 int i=0;
7 while(!stopRequested){
8 i++;
9 }
10 }
11 });
12 backgroundThread.start();
13
14 TimeUnit.SECONDS.sleep(1);
15 stopRequested = true;
16}
1+---- cpu core ---+ +---- cpu core ---+
2| Main Thread | | backgroundThread|
3| +-------------+ | | +-------------+ |
4| | cpu cache | | | | cpu cache | |
5| | (false) | | | (false) | |
6| +-------------+ | | +-------------+ |
7+-----------------+ +-----------------+
8 ▲ ▲
9 | |
10 | |
11+----------------------------------+
12| RAM |
13| (stopRequested : false) |
14+----------------------------------+

어플리케이션이 실행되면서 메인메모리로부터 초기화된 stopRequested의 false값을 각 쓰레드마다 독립적인 공간인 CPU캐시 영역에 저장하게 된다. 이후 각 쓰레드별고 데이터를 읽거나 쓸때 thread별 독립적인 공간인 cpu cache에서 데이터를 처리하기 때문에 메모리 가시성 문제가 발생하게 된다.

1+---- cpu core ---+ +---- cpu core ---+
2| Main Thread | | backgroundThread|
3| +-------------+ | | +-------------+ |
4| | cpu cache | | | | cpu cache | |
5| +-------------+ | | +-------------+ |
6+--------|---------+ +-----------------+
7 | writeread
8 | true | true
9|
10+----------------------------------+
11| RAM |
12| (stopRequested : false -> true) |
13+----------------------------------+

4. 주의사항

1) volatile은 메인 메모리로부터 읽고 쓰기 때문에 성능저하가 발생할 수 있다
2) 쓰기 쓰레드가 1개, 읽기 쓰레드가 다수일 경우에 가시성 확보가 가능하다.
volatile은 변수에 읽거나 쓸 때 블록시키지 않기 떄문에 다수의 쓰레드가 수정하고 다수의 쓰레드가 읽으면 동시성 보장이 안된다.

1public class SerialNumber {
2
3 private volatile static int NUMBER;
4
5 public static int increase(){
6 return NUMBER++;
7 }
8
9 public static void main(String[] args) throws InterruptedException {
10
11 int count = 100;
12 ExecutorService service = Executors.newFixedThreadPool(10);
13 CountDownLatch countDownLatch = new CountDownLatch(count);
14 for(int i=0; i<count; i++){
15 service.execute(() ->{SerialNumber.increase();
16 });
17 countDownLatch.countDown();
18 }
19 countDownLatch.await();
20 System.out.println(SerialNumber.increase());
21 }
22}
1[출력결과]
2첫번째 시도 - 49
3두번째 시도 - 85

여러 쓰레드에서 쓰기를 하게되면 다른 쓰레드가 메인메모리에 업데이트를 하기 전에 또 다른 쓰레드가 데이터를 읽어온 후 데이터를 업데이트 하여 데이터가 중복된 값이 발생될 수 있다.

5. 정리