본문 바로가기
Java/Fundamental

Volatile 변수에 대한 이해 (feat. 싱글톤)

by BestUgi 2018. 4. 17.

Volatile 변수가 무엇인지, 언제 사용하는지에 대해서 공유하고자 합니다.


Volatile 변수란?


Volatile로 선언된 변수는 메인 메모리에서 CPU의 캐시에 적재되지 않는 변수를 의미하며, 주로 여러 스레드가 동시에 접근할 수 있는 변수를 Volatile로 선언합니다.


Volatile 변수의 특징은 아래와 같습니다.

- Volatile 변수는 CPU의 Cache를 거치지 않고 메인 메모리에 직접 Read/Write를 수행합니다. - Volatile 변수에 대한 접근(Read/Write)은 Synchronized를 사용하는 것과 동일하게 동작합니다.

- Primitive 타입과 Object 타입(Null 허용) 모두 사용 가능합니다.


Java 5 이후 부터는, 한가지 특징이 더 추가 되었습니다.


Volatile 변수를 사용할 경우, 변수 접근 코드는 메모리 장벽 코드를 생성합니다. 
즉, Volatile 변수를 사용할 경우, 해당 변수에 접근하는 코드에 대해서는 최적화를 수행하지 않습니다.


Volatile 변수를 가장 많이 사용하는 곳은 Thread 간의 Running Flag가 아닐까 싶습니다.


public class MyThread extends Thread {     private volatile boolean keepRunning;     @Override     public void run() {     while(keepRunning) {     // Your Codes...      }     }     public void stopIt() {     keepRunning=false;     } }


keepRunning 변수를 volatile로 선언한 이유는, stopIt 함수를 다른 스레드에서 호출 할 수 있기 때문입니다. keepRunning 변수를 volatile로 선언하지 않을 경우, 다른 스레드에서 stopIt 함수를 호출하여 keepRunning의 값을 false로 변경 하였음에도 불구하고 MyThread의 'while(keepRunning)' 구문의 최적화 상태에 따라서 Cache에 있는 keepRunning 값만을 참조할 수 있습니다.

javamex의 volatile에 대한 설명을 참고 하면, 해당 스레드는 영원히 종료하지 않을 수 있다고 설명하고 있습니다. "If the variable were not declared volatile (and without other synchronization), then it would be legal for the thread running the loop to cache the value of the variable at the start of the loop and never read it again. If you don't like infinite loops, this is undesirable.".


조금 더 자세히 설명하자면, keepRunning 변수를 volatile로 선언하지 않을 경우, 아래와 같은 코드로 최적화 될 수 있습니다.

public void run() {     if(keepRunning) {         while(true) {             // Your codes..         }     } }


자바 4 이전 버전에서는 싱글톤 패턴 생성에 대해서 Double Checked Locking 기법을 사용하여 구현할 경우 생성이 되다만 객체를 다른 클래스에서 참조 할 수 있는 문제가 있습니다. 하지만 자바 5 이후 버전에서는 volatile 변수를 사용할 경우 메모리 장벽 특성으로 인해서 싱글톤 패턴에 대해서 Double Checked Locking(DCL) 패턴을 사용할 수 있게 되었습니다.(물론 싱글톤을 굳이 DCL  패턴으로 구현하지 않고 더 효율적으로 구현할 여러 가지 있습니다.) 효율적인 DCL 패턴의 싱글톤 객체 생성 코드는 아래에 있습니다.

class MyClass {     public static volatile MyClass instance;     

public staitc MyClass getInstance() {

MyClass ref = instance; if (ref == null) {

synchronized(MyClass.class) {

if (instance == null) {

ref = instance = new MyClass();

}

}

}

return ref;

}

}


위의 코드는 DCL 패턴을 사용한 싱글턴 객체를 생성하는 방법입니다. 위의 코드에서 ref 변수를 사용하는 이유는 무엇일까요? 그 이유는 volatile 변수의 접근 속도가 느리기 때문에 ref 변수에 저장하여 volatile 변수 접근을 최대한 줄이고자 유도하는 것입니다. volatile 변수와 일반 변수를 선언한 이후에 반복해서 값을 증가시키는 코드를 수행하였을 경우, 그 성능을 비교해 보면 위의 코드에서 ref 변수를 사용하는 이유를 금방 알 수 있을 것입니다.


위에서 설명한 volatile 변수의 단점과 사용하지 말아햘 상황을 정리하면 아래와 같습니다.

[volatile 변수 단점 및 한계] - 접근 성능이 느림 (캐시 사용 안하므로) - 단지 변수의 접근(read/write)에 대해서만 정합성 보장


[Bad Use Case] - Immutable 객체 - 싱글 스레드에서만 접근하는 변수 - 동기화가 필요한 변수 접근. e.g. 변수를 read-update-write 하는 코드 --> synchronized or lock 사용

지금까지 Volatile 변수에 대한 정리였습니다. Volatile의 가장 중요한 메모리 장벽(최적화 안함), Memory 직접 접근의 특징을 사용하여 꼭 필요한 곳에만 사용하시기 바랍니다.



[References]

http://jpbempel.blogspot.kr/2012/10/volatile.html

https://mechanical-sympathy.blogspot.kr/2013/02/cpu-cache-flushing-fallacy.html

https://dzone.com/articles/java-multi-threading-volatile-variables-happens-be-1

댓글