volatile
1. volatile
- 메모리 값과 캐시 값과의 차이가 발생할 때 volatile을 사용한다.
- 이 volatile은 싱글 코어 프로세서에서는 문제가 발생하지 않으므로 쓰일 곳이 없지만, 멀티 코어 프로세서에서
많이 쓰일 것이다. 그 이유는 멀티코어 프로세서마다 각 캐시가 존재하기 때문이다.
- 코어는 메모리에서 읽어온 값을 캐시에 저장하고 캐시에서 값을 읽어서 빠르게 작업한다. 그러다보니,
값을 읽어올 때 캐시에 값이 존재하는지 확인하고 없을 때에만 메모리에 가서 값을 읽어온다.
- 만약 코어가 메모리에 값이 변경되었는데도 캐시 값을 쓰인다면, 의도와 다른 값을 가질 수 있다.
- 그러나 캐시에 사용되는 변수 앞에 volatile을 쓴다면, 코어가 캐시가 아닌 메모리에 가서 읽어오기 때문에
해결할 수 있다.
- 추가적으로 synchronized 블럭을 사용해도 같은 효과를 얻을 수 있다. 그 이유는 캐시와 메모리간의 동기화가
이루어지기 때문에 값의 불일치가 해소되기 때문이다.
2. volatile로 long과 double을 원자화
- JVM은 데이터 처리를 4 byte단위로 처리하기 때문에, int와 int보다 작은 타입들은 한번에 읽거나 쓰는 것이
가능하다. 즉, 명령어를 쪼개거나 나눌 수 없는 최소의 작업단위라는 것이다. 그러므로, 다른 쓰레드가 끼어들 수 없다.
- 그러나 만약 long이나 doubl인 경우 8 byte이므로, 다른 쓰레드가 끼어들 여지가 있다. synchronized를
사용하여 메서드를 감싸서 처리할 수 있지만, 더 간단하게 volatile을 붙이면 된다.
( 단, 상수에는 붙일 수 없다. )
- volatile에서의 원자화란, 더 이상 작업을 나눌 수 없다는 뜻이다. 또한 synchronized 블럭도 일종의 원자화라고
볼 수 있다.
- 주의할 점은 volatile은 동기화를 하는 것은 아니다. 아래의 예제를 보자.
volatile long balance;
// balance를 원자화한다.
synchronized int getBalance(){
return balance;
}
synchronized void withdraw(int money){
if(balance >= money){
balance -= money;
}
}
( balance를 원자화 했기 때문에 getBalance를 동기화할 필요 없다고 생각할 수 있다. )
( 그러나 만약 withdraw(출금)을 실행하면 lock을 걸고 getBalance()를 호출할 수 있게 된다, )
( 그러므로, 출금이 진행중일 때는 기다렸다가 출금이 끝난 후에 잔고를 조회할 수 있게 해야한다. )