Java

volatile키워드와 동기화, Atomic 타입

멋진그이름 2014. 11. 7. 12:11

<개요>

volatile은 컴파일러가 특정변수에 대해서 옵티마이져에 대해서 캐슁을 하지 않고, 리오더링을 하지 않도록 해주는 키워드라고 한다.

그로 인해 멀티쓰레드 환경에서 동기화문제를 만날 때 사용하는 경우가 많다.

 

<내용>

예를 살펴보자

기본적으로 자바기본형은 thread-safe하지만 개발환경에 따라 특이한 경우가 발생할 수 있다.

32bit환경에서 long이나 double처럼 64bit 값을 처리할 때 assign이 두단계에 걸쳐서 일어나게 된다. 

(address의 한계때문에 발생한다.


volatile키워드를 사용할 경우 이에 대한 원자성을 보장하여 오류를 방지해준다.


그렇다면 synchronized 는 언제 사용할까? 위와 같이 volatile로 선언하더라도

public void add(int i){

    longResult += i;

}

와 같은 메소드를 수행하면 + 연산까지 원자성을 보장하지는 않기 때문에 마찬가지로 오류가 발생할 수 있다.


이때 add 메소드에 대해서 synchronized 키워드를 사용하여 동기화를 적용한다.


하지만 해보신분들은 알겠지만 synchronized를 사용하는 순간 엄청나게 느려진다. 어찌보면 당연한 결과이다...

 

<정리>

이를 보완하기 위해서 JDK 1.5이후에서는 Atomic 타입을 제공

Class Description
AtomicBoolean
A boolean value that may be updated atomically.
AtomicInteger
An int value that may be updated atomically.
AtomicIntegerArray
An int array in which elements may be updated atomically.
AtomicIntegerFieldUpdater<T>
A reflection-based utility that enables atomic updates to designated volatile int fields of designated classes.
AtomicLong
A long value that may be updated atomically.
AtomicLongArray
A long array in which elements may be updated atomically.
AtomicLongFieldUpdater<T>
A reflection-based utility that enables atomic updates to designated volatile long fields of designated classes.
AtomicMarkableReference<V>
An AtomicMarkableReference maintains an object reference along with a mark bit, that can be updated atomically.
AtomicReference<V>
An object reference that may be updated atomically.
AtomicReferenceArray<E>
An array of object references in which elements may be updated atomically.
AtomicReferenceFieldUpdater<T,V>
A reflection-based utility that enables atomic updates to designated volatile reference fields of designated classes.
AtomicStampedReference<V>
An AtomicStampedReference maintains an object reference along with an integer "stamp", that can be updated atomically.

public class AtomicLong extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 1927816293512124184L;

    // setup to use Unsafe.compareAndSwapLong for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

<중략>

private volatile long value;  /**
     * Creates a new AtomicLong with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicLong(long initialValue) {
        value = initialValue;
    }

/**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final long getAndAdd(long delta) {
        return unsafe.getAndAddLong(this, valueOffset, delta);
    }

 a. 예를 들면Long대신에 AtomictLong을 사용하고 제공되는 addAndGet메소드를 사용할 경우 멀티쓰레드 간의 값을 보장하면서 속도도 매우 빨라진다.


 b. 내부적으로 코드를 살펴보면 AtomicLong은 Number를 확장하며 Serializable을 impl한다 (어찌보면 당연하다..)

 c. 멤버변수로 private volatile long value를 가지고 있으며 addAndGet메소드는 final로 선언되어 무한루프를 반복한다.

 d. Unsafe class의 compareAndSwapLong 메소드를 활용한 compareAndSet메소드를 사용한다. 모든 연산메소드가 동일하다.

 

<참고사이트>

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html