Language/Java

Java - Atomic Variable (+ 동시성 제어 비교 with volatile, synchronized)

JaeHoney 2022. 2. 12. 11:59
반응형

동시성 제어 in 자바

자바에서 Multi Thread 환경의 동시성을 제어를 위한 방법은 대표적으로 3가지가 있습니다.

  • volatile
  • synchronized
  • Atomic variable

 

3가지 방법은 모두 쓰임새가 다른 데 같이 살펴보겠습니다!

volatile

volatile을 키워드를 사용하면 멀티 쓰레드 환경에서 변수를 공유할 수 있습니다.

public class SharedObject {
    public volatile int counter = 0;
}

단, 여러 개의 스레드가 읽을 수 있지만, write할 수 있는 쓰레드는 하나만 존재해야 합니다.

 

 

Volatile 키워드는 각 스레드가 가진 CPU cache가 아닌, 공유하는 Main memory에서 읽고 Write하는 방법입니다.

 

즉, A클래스에서 count라는 변수가 있고, 쓰레드 2개가 해당 count 변수로 뭔가 작업을 한다고 가정합니다. Volatile 키워드 없이는 각 쓰레드에서 CPU 캐시에서 count를 따로 가지고 있습니다. 공유가 안됩니다.

 

그래서 Volatile 키워드로 변수를 선언해서 여러 개의 쓰레드에서 공유할 수 있도록합니다.

 

Volatile은 Write를 하나의 쓰레드에서만 할 때 유용하고 여러 쓰레드에서 Write를 한다면 부적절합니다. 여러 개의 변수가 동시에 읽고 그 시점을 기준으로 데이터를 변경할 때 혼란이 생기기 때문입니다.

 

synchronized

synchronized를 사용하면 block에 쓰레드가 참조할 동안에는 절대로 다른 쓰레드가 해당 block을 참조하지 못하게 막습니다.

private static long number = 0;

public static synchronized void increase() {
    number++;
    System.out.println(number);
}

가장 안전한 방법이지만 문제는 해당 메서드 블록 전체에 Lock이 걸린다는 것입니다. 또한 Blocking을 구현하고, 스레드를 준비나 실행 상태로 변경시키면서 심각한 performance저하가 생긴다는 단점이 있습니다.

 

Atomic variable

Automic variable(AtomicInteger, AtomicLong, AtomicBoolean 등)을 사용하면 CAS(compare-and-swap)알고리즘을 이용해서 블록에 Lock을 걸지 않고도 synchronized보다 효율적으로 원자성을 보장합니다.

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

    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long VALUE
        = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;
}

public final class Unsafe {
    @HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
            return v;
    }
}

write를 할 때 CAS 알고리즘을 통해 value의 최신 상태를 보장하고 volatile 키워드를 포함한 value를 사용하여 쓰기의 동시성도 보장합니다.

 

CAS 알고리즘은 현재 쓰레드가 가진 값메모리가 가진 값을 비교하고 다르면 현재 스레드의 값을 동기화합니다.

private static AtomicLong number = new AtomicLong(0);

public static void increase() {
    System.out.println(number.incrementAndGet());
}

그래서 asynchronized의 성능 문제를 해결하면서도 멀티 쓰레드의 동시 접근 문제를 해결할 수 있습니다.

 

감사합니다.

 

 

반응형