본문

171211(월) - @Volatile

@Volatile


http://tutorials.jenkov.com/java-concurrency/volatile.html


- Java keyword

- variable을 'being stored in main memory' 로 mark 하도록 쓰임

- every read of a volatile variable in main memory, not from the CPU cache.

- every written to main memory, not just to the CPU cache


Visibility guarantee

- volatile keyword는 across threads간의 variables visibility를 보장한다.

- thread가 non_volatile variables에서 작동하는 multi threaded application에서는 performance상의 이유로 각 thread가 variables를 main memory에서 CPU cache로 copy할 수 있다.


- computer에 둘 이상의 CPU가 포함되어 있으면 각 thread가 다른 CPU에서 실행될 수 있다.

- non-volatile variable을 사용하면 JVM이 main memory에서 CPU  cache로 데이터를 읽는 시점을 보장할 수 없으며, CPU cache의 내용을 main memory에 write하게 된다.

Threads may hold copies of variables from main memory in CPU caches.


- variable이 아직 다른 thread에 의해 main memory에 다시 write되지 않았기 때문에 variable의 최신값을 보지 못하는 thread problem을 'Visibility problem' 이라고 한다.

- 한 thread의 updates는 다른 thread에서 보지 못한다.

The CPU cache used by Thread 1 and main memory contains different values for the counter variable.


Volatile happen before guarantee

- Volatile keyword guarantees this

1. thread A가 volatile variable을 write하고 이후에 thread B가 동일한 volatile variable을 read하면, volatile변수를 write 하기전에 thread A가 볼수 있는 모든 variable을 변수를 읽은후에 thread B에서도 볼 수 있다.


2. volatile variable의 read, write instructions는 JVM에서 reordered 할 수 없다.


•thread가 volatile variable에 쓰면 variable itself 뿐만아니라 main memory에도 write된다.

•volatile variable에 쓰기전에 thread에 의해서 변경된 모든 다른 variable은 main memory로 flushed 된다.

•thread가 volatile variable을 읽으면 volatile variable과 함께 flushed된 main memory의 다른 모든 변수도 읽게 된다.


Thread A:
    sharedObject.nonVolatile = 123;
    sharedObject.counter     = sharedObject.counter + 1;

Thread B:
    int counter     = sharedObject.counter;
    int nonVolatile = sharedObject.nonVolatile;

- thread A는 volatile counter에 쓰기전에 non-volatile인 nonVolatile을 작성하므로 counter가 main memory에 write 될 때 둘 다 쓰여지게 된다.

- thread B는  main memory에서 thread B가 사용하는 CPU cache로 읽는다.

- thread B가 nonVolatile을 읽을 때 까지 thread A가 작성한 값을 볼 수 있다.

- each variable을 volatile로 선언하는 대신 하나, 또는 소수만 volatile로 선언해야 한다.


public class Exchanger {

    private Object   object       = null;
    private volatile hasNewObject = false;

    public void put(Object newObject) {
        while(hasNewObject) {
            //wait - do not overwrite existing new object
        }
        object = newObject;
        hasNewObject = true; //volatile write
    }

    public Object take(){
        while(!hasNewObject){ //volatile read
            //wait - don't take old object (or null)
        }
        Object obj = object;
        hasNewObject = false; //volatile write
        return obj;
    }
}

- thread A는 put을 호출하여 time to time object를 넣을 수 있다.

- thread B는 take를 호출하여 object를 수시로 가져갈 수 있다.

- 이 코드는 thread A만 put을 호출하고, thread B는 take만 하는 경우에 한해 volatile variable을 사용하여 정상 작동 가능 (without synchronized block)

- but JVM은 reordered instuctions의 의미를 변경하지 않고 JVM이 수행 가능한 경우, Java instructionis의 oreder를 reorder하여 performence를 최적화 할 수 있다.


while(hasNewObject) {
    //wait - do not overwrite existing new object
}
hasNewObject = true; //volatile write
object = newObject;

- 새로운 object가 set되기 전에 volatile variable이 write 된다.

- JVM이 보기에 valid 하다.

- 두개의 insturctions는 의존하지 않는다.

- instruction ordering 을 변경하면 object variable의 visibility가 harm된다.

1. thread A는 실제로 object를 설정하기 전에 hasNewObject를 true로 만들어버린다.

2. object가 언제 flush될지에 대한 guarantee가 없다.


- 위와같은 현상을 방지하기 위해서 volatile은  'happens before guarantee'를 제공

- guarantee 이전에 발생한 일은 volatile variable의 read, write instructions를 reordered 할 수 없음을 보장

- before and after에 대한 instructions는 reorder 가능하지만 volatile read, write instructions는 reorder 불가능

sharedObject.nonVolatile1 = 123; sharedObject.nonVolatile2 = 456; sharedObject.nonVolatile3 = 789; sharedObject.volatile = true; //a volatile variable (이후 재정렬 불가능) int someValue1 = sharedObject.nonVolatile4; int someValue2 = sharedObject.nonVolatile5; int someValue3 = sharedObject.nonVolatile6;


Volatile is not always enough

- 여전히 volatile 설정으로는 부족한 부분이 남아있다.

- thread A만 shared counter variable에 write하는 상황에서 counter variable을 volatile로 설정하면 thread B가 항상 최신값을 볼 수 있음

- thread가 먼저 volatile variable의 값을 읽어야 하고 해당 값을 기반으로 shared volatile variable에 대한 새 값을 생성하면 volatile variable이 더 이상 정확한 visibility를 보장하지 못한다.

- volatile variable의 reading and new value writing 사이의 short time gap은 여러 thread가 동일한 volatile 값을 읽을 수 있는 race condition 을 만들면서 서로의 값을 읽고 덮어쓴다.


- 다음은 main memory로부터 counter variable을 읽고 cache에서만 counter를 서로 증가시키지만 main memory의 counter는 여전히 0인 경우

- threadA와 thread B는 이제 sync되지 않는다.

Two threads have read a shared counter variable into their local CPU caches and incremented it.


When is volatile enough?

- 두 thread가  shared variable을 read and write 하는경우 volatile을 쓰기에 적절하지 않다.

- 위와같은 경우에는 synchronized 를 사용하여 read and write가 atomic임을 보장해야 한다.


- 하나의 thread만이 volatile variable의 값을 read and write하고 다른 thread는 variable을 only read 한다면 read thread는 volatile variable에 쓰여진 latest value를 볼 수 있다.

- volatile variable은 32, 64 bit variable에서 working 하도록 보장된다.



Performance considerations of volatile

- main memory read, write는 CPU cache에 access하는것보다 expensive하다.

- volatile variable은 JVM의 reordered도 방지하므로 성능 저하가 발생 가능하다.

'Mobile > RxJava2' 카테고리의 다른 글

171214(목) - RxJava2 (defer)  (0) 2017.12.14
171213(수) - RxJava2 (create)  (0) 2017.12.13
171211(월) - @Target  (0) 2017.12.11
171208(금) - @Qualifiers  (0) 2017.12.08
171206(수) - @Scope, @Retention  (0) 2017.12.06

공유

댓글