ㄷㅣㅆㅣ's Amusement
[Android/Java] 변수를 Volatile로 선언하면? 본문
[Android/Java] 변수를 Volatile로 선언하면?
반응형
Volatile
멀티 쓰레드에서 volatile의 사용
(멀티 쓰레드 또는 병렬처리 부분은 다른 포스트를 참조하세요.)
사실 멀티 쓰레드 프로그래밍을 하더라도 volatile을 잘 쓸 기회가 없었기에 만 8년동안 개발에 있으면서도 정확한 발음을 알지 못했다.
"발러틀"이라고 읽도록 하자 ㅡ,.ㅡ;;
- non-volatile로 선언했을 때 문제가 되는 경우1234567891011121314public class testVolatile {private int mCount = 0;public void runCountThread() {new Thread(new Runnable() {@Overridepublic void run() {for (;;) {Log.d("Test Volatile", "count : " + mCount++;);}}}).run();}}
cs - 위의 코드로 예를 들어보자. runCountThread()메소드를 두번 호출하면 어떻게 될까?
- 각각의 thread는 mCount의 값을 출력하고 1씩 증가시킨다.
- 그런데 여기서 나오는 로그들이 1,2,3,4,5,6,7... 순으로 의도대로 출력될까??
- 위의 코드를 작성하고 실행해본다면 count의 값이 순차적이지 않고 1,1,2,2,3,3,4,4,5,5... 와 같이 출력되는 것을 볼 수 있다.
- non-volatile로 선언시 문제가 발생하는 이유
- 일반적으로 변수에 값을 할당하면 위와같이 먼저 CPU의 캐쉬에 저장하게 된다.
- 이후 Memory에 쓰게 되는데, 이 메모리에 쓰는 작업은 OS/Platform에 따라 다르고, 이 시점은 프로그래머가 감지할 수 없다.
- 따라서, 각기 다른 CPU(또는 Core, 또는 물리적 thread)에서 동작하는 thread는 최초에 mCount값(0)을 Memory로 부터 가져와서 Cache에 저장되어있는 값을 대상으로만 +1을 해준다.
- +1을 해주더라도 Cache에서 언제 메모리로 쓰여지는지에 대한 컨트롤은 프로그래머가 할 수 없다.
- 로그를 찍어보면 각자 자기가 가지고있는 캐쉬영역의 값을 찍기 때문에 같은 값이 겹쳐서 보여진다. (물론 상황에 따라서 OS/Platform이 연산하기 전에 Memory에 썼고, 이후 다른 쓰레드에서 메모리에서 가져오는 동작을 했다면 겹치지 않는 영역이 생길 수도 있다. 아무튼 이경우도 문제가 없는 것은 아니니..)
- 이런 경우를 방지하기 위해 사용하는 것이 Volatile이고, 이런 문제를 "가시성(Visibility)"문제라고 한다.
- Volatile을 사용하여 순서대로 카운트하기1234567891011121314public class testVolatile {private volatile int mCount = 0;public void runCountThread() {new Thread(new Runnable() {@Overridepublic void run() {for (;;) {Log.d("Test Volatile", "count : " + mCount++;);}}}).start();}}
cs - 이렇게 volatile로 선언하면 이후 모든 cache에 있는 값을 변경하는 작업에 대해서 memory로 쓰는 것을 보장한다.
- 당연히 값을 읽어올 때마다 memory에서 읽어오는 것을 보장.
- Volatile로 선언한 멤버변수의 또다른 속성
- 최적화 방지12345678public void testVolatile() {int a = 0;for (int i=0; i<100; i++) {a++;}Log.d("Test Volatile", "a : " + a);}
cs - 위의 코드를 보자. 프로그래머의 의도는 a를 100까지 순차적으로 증가시킨 후 a의 값을 출력하는 것이다.
그러나 위의 코드는 JVM에 의해서 (C/C++에서는 컴파일러에 의해서) 다음과 같이 바뀐다
1234567public void testVolatile() {int a = 0;a = 100;Log.d("Test Volatile", "a : " + a);}cs - 어떠한 상황에서도 a가 100이 아닌 값을 가질 수 없어졌다는 것.
- volatile로 선언하면 이러한 최적화에 의한 의도변경도 일어나지 않는다.
- synchronized vs volatile12345678910111213141516public class testVolatile {private volatile int mCount = 0;private int a = 0;public void runCountThread() {new Thread(new Runnable() {@Overridepublic void run() {for (;;) {a += ++mCount;Log.d("Test Volatile", "count : " + a);}}}).start();}}
cs - 이번에도 위의 예제에 있는 runCountThread()를 두번 이상 호출했다고 가정하자.
- 어떤 결과가 나오게 될지 유추하는 것이 좀 복잡하다.
- 10행의 코드는 연산이 단 한번으로 끝나는 것이 아니라 다음의 4가지 동작이 일어나기 때문이다. (만약 32비트cpu에서 long으로 선언한 변수였다면 더 많은 동작이 일어난다)
- mCount를 1 증가
- mCount를 int로 캐스팅
- a 와 mCount를 더한다
- 3번의 값을 a에 할당한다.
- 따라서 multi thread환경에서 a의 값을 출력한다면, 어떤 쓰레드는 mCount를 더한값을, 어떤 쓰레드는 a의 값을 프로그래머가 의도하지도 않았고, 컨트롤 할 수도 없게 나타내게 된다.
- 이 경우에는 volatile 대신 synchronized를 쓴다면 10번 줄의 모든 연산까지도 원자성을 보장하게된다.
반응형
'Programming > Android' 카테고리의 다른 글
[Android/Java] Glide, source analysis. -- 2. Flow (0) | 2016.12.09 |
---|---|
[Android/Java] 병렬 프로그래밍 : Executor Framework에대한 고찰 ----- 3 (0) | 2016.12.07 |
[Android/Java] 병렬 프로그래밍 : Executor Framework에대한 고찰 ----- 2 (2) | 2016.12.05 |
[Android/Java] 병렬 프로그래밍 : Executor Framework에대한 고찰 ----- 1 (0) | 2016.12.01 |
[Android/Java] Glide, An Image Loader. -- 1. Overview (0) | 2016.11.02 |
Comments