보통 동시성 이슈의 많은 원인은 값을 조회하고 값을 변경하는 과정이 원자적이지 않기 때문에 발생한다.
자바에서 이러한 연산의 원자성을 지원하기 위해 AtomicInteger
와 같은 클래스를 지원한다.
해당 연산이 더 이상 나눌 수 없는 단위로 수행되는 경우 이를 원자적 연산이라 한다.
i++
과 같은 연산도 i = i + 1
의 축약이므로 원자적인 연산은 아니다.
멀티 스레드를 사용하는 경우 원자적 연산은 문제가 되지 않아 대체적으로 문제가 되는 연산은 원자적이지 않은 연산이다.
락을 사용하지 않고 원자적인 연산을 수행할 수 있게 하며, CAS(Compare-And-Swap, Compare-And-Set) 연산 또는 락 프리 기법이라고도 한다.
소프트웨어가 아닌 CPU 하드웨어에서 지원하는 기능이다.
CAS 연산을 사용하는 자바 라이브러리 중 일부이다.
java.util.concurrent
패키지atomic
패키지 내 클래스들은 CAS 연산을 기반으로 동작한다.ForkJoinPool
도 이 연산을 기반으로 동작한다.Spring Webflux를 사용하게 되면 Reactor와 Netty를 접하게 되는데, 해당 라이브러리 모두 CAS 연산을 사용한다는 점을 새롭게 알게 되었다.
AtomicInteger
클래스멀티 스레드에서 안전한 연산을 지원하기 위한 자바의 클래스로 JDK 1.5부터 지원되었다.
내부적으로 volatile
타입의 int
변수를 가지고 있으며, 해당 변수로 연산을 진행한다.
숫자 기반 클래스를 다루는 도구와 유틸리티에서 동일하게 사용 가능하도록 Number
인터페이스를 확장했다.
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
// 중략
}
get()
public final int get()
set()
public final void set(int newValue)
newValue
로 값을 세팅compareAndSet()
public final boolean compareAndSet(int expectedValue, int newValue)
expectedValue
와 일치하면 newValue
로 변경incrementAndGet()
public final int incrementAndGet()
decrementAndGet()
public final int decrementAndGet()
Intger
의 원자적 연산을 지원하는 AtmoicInteger
외에도 각 자료형별로 아래와 같이 클래스가 존재한다.