volatile이 선언되 변수는 즉시 메인 메모리에 저장할 것을 의미합니다.
모든 volatile 변수는 컴퓨터의 메인 메모리로부터 읽기하고, volatile 변수에 대한 쓰기 작업은 CPU 캐시가 아닌 메인 메모리에 이루어집니다.
멀티스레드 환경에서의 non-volatile 변수에 대한 작업은 성능상의 이유로 CPU 캐시를 사용합니다.
이때, 둘 이상의 CPU가 탑재된 컴퓨터에서 어플리케이션을 실행한다면, 각 스레드는 변수를 각 CPU의 캐시로 복사형 읽습니다.
(출처 : http://tutorials.jenkov.com/java-concurrency/volatile.html)
이러한 환경에서 non-volatile 변수는 JVM이 메인 메모리로부터 CPU 캐시로 변수를 읽어들이거나 CPU 캐시로부터 메인 메모리에 데이터를 쓸 때 원자성에 대한 보장을 할 수 없습니다.
아래는 위와 같은 상황에서의 문제발생 예시 입니다.
public class Example{
public int cnt = 0;
}
조건 1 : Thread1은 cnt 변수를 증가시킨다.
조건 2 : Thread1과 Thread2는 때에 따라서 cnt 변수를 읽는다
위와 같은 예시에서 cnt 변수에 volatile 키워드가 없다면, cnt 변수가 언제 CPU 캐시에서 메인 메모리로 쓰일지 알 수 없습니다.
즉, CPU 캐시의 cnt변수와 메인 메모리의 cnt 변수가 다른 값을 가질 수 있는 상태인 것 입니다.
(출처 : http://tutorials.jenkov.com/java-concurrency/volatile.html)
이러한 상황을 가시성 문제라고 합니다.
스레드가 변경한 값이 메인 메모리에 저장되지 않아서 다른 스레드가 이 값을 볼 수는 상황이라는 의미입니다.
이러한 가시성 문제를 해결하기 위해 사용하는 것이 바로 volatile입니다.
만약 cnt 변수에 volatile 키워드를 선언한다면 이 변수에 대한 쓰기 작업은 즉시 메인 메모리에 이루어 질 것이고, 읽기 작업 또한 메인 메모리로부터 다이렉트로 이루어지게 됩니다.
스레드가 volatile 변수의 초기 값을 필요로 할 때, 그리고 volatile 변수의 새 값이 이 초기값을 근거로 할 때 volatile 선언은 더 이상 가시성을 보장하지 못합니다.
왜냐하면 volatile 변수를 읽고, 새 값을 쓰는 사이의 짧은 순간에 경합이 발생할 수 있으며 이로인해 다수의 스레드가 volatile 변수의 값을 똑같이 읽고, 새 값을 생성하여 이를 메인 메모리로 저장하는 동안 서로의 값을 덮어 쓸 수 있기 때문입니다.
예시로 여러 스레드가 동시에 cnt 값을 증가시키는 상황이 volatile 변수가 불완전해지는 상황이라고 할 수 있습니다.