프로그래밍을 하다보면 멀티 스레드를 사용해야하는 경우를 많이 만나게 됩니다.
아시다시피 멀티 스레딩 프로그래밍에서는 기본적으로 고려해야하는 문제가 바로 동시성 문제, 즉 동기화를 어떻게 할 것인가인데요!
public class Example {
private boolean locked = false;
public boolean lock() {
if(!locked) {
locked = true;
return true;
}
return false;
}
}
예를 들어, 위와 같은 코드를 멀티 스레드 환경에서 동시에 lock() 메소드를 실행하게 된다면 자칫 하나의 스레드가 아닌 여러 스레드에서 동시에 true를 리턴하게 될 수 있습니다.
이는 멀티 스레드 환경에서 발생할 수 있는 많은 문제들 중 경쟁 상태(race condition)을 고려하지 못해 발생하는 문제입니다.
자바는 이러한 문제를 해결하기 위해 다양한 방법을 제공하는데요! 대표적으로 Atomic Type, volatile, synchronyzed가 있습니다.
이번 포스팅에서는 위 방법들 중 Atomic Type에 대해서 알아보도록 하겠습니다.
CAS(Compare And Swap) 방식
Atomic은 CAS 방식에 기반하여 동기화 문제를 해결합니다.
그렇다면 CAS란 무엇일까요? CAS란 변수의 값을 변경하기 전에 기존에 가지고 있던 값이 내가 예상하던 값과 같을 경우에만 새로운 값으로 할당하는 방법입니다.
코드로 예를 들자면 아래와 같습니다.
public class AtomicExample {
int val;
public boolean compareAndSwap(int oldVal, int newVal) {
if(val == oldVal) {
val = newVal;
return true;
} else {
return false;
}
}
}
위 코드를 보니 이해가 더 빠르죠?
한 마디로 CAS는 값을 변경하기 전에 한 번 더 확인하는 거라고 보시면 되겠습니다.
Java에서 제공하는 Atomic Type은 이러한 CAS를 하드웨어(CPU)의 도움을 받아 한 번에 단 하나의 스레드만 변수의 값을 변경할 수 있도록 제공하고 있습니다.
그럼 이제 원리를 파악 했으니, 대표적인 Atomic Type인 AtomicBoolean과 AtomicInteger를 순서대로 살펴보겠습니다.
참고로 Atomic Type은 java.util.concurrent.atomic 패키지에 있습니다.
AtomicBoolean
AtomicBoolean도 결국엔 boolean이기 때문에 가질 수 있는 값은 true/false 밖에 없어 간단합니다.
AtomicBoolean이 제공하는 메소드 중 대표적인 두 가지 메소드만 살펴보겠습니다.
(이외에 다양한 메소드가 궁금하신 분들은 javadoc을 참고하세요)
먼저 살펴볼 메소드는 CAS 알고리즘을 사용해 Atomic하게 값을 변경해주는 메소드인 compareAndSet(boolean expect, boolean update) 입니다.
예제부터 살펴보겠습니다.
public class AtomicExample {
private AtomicBoolean locked = new AtomicBoolean(false);
public boolean lock() {
return locked.compareAndSet(false, true);
}
}
위 코드는 가장 위에서 살펴본 예제를 변형한 코드입니다. 우선 lock() 메소드가 굉장히 간결해졌죠?
compareAndSet(boolean expect, boolean update) 메소드는 expect 값과 비교해 같을 경우에만 update로 값을 변경합니다.
이때 주의사항으로는, return 값이 AtomicBoolean 변수의 현재 값을 리턴하는게 아니라 현재 값이 expect와 같을 경우 true, 그렇지 않을 경우 false를 리턴합니다.
다음으로 살펴볼 메소드는 getAndSet(boolean update)입니다.
이 메소드는 이전 값을 리턴하고 새로운 값으로 Atomic하게 update합니다.
한 마디로 내가 새로운 값으로 update를 하되 변경 이전의 값을 가져오고 싶을 때는 바로 이 getAndSet() 메소드를 사용하면 되겠습니다.
AtomicInteger
AtomicBoolean으로 boolean을 알아봤으니 이제 int의 Atomic Type인 AtomicInteger를 알아보겠습니다.
AtomicInteger의 경우 아무래도 true/false 두 가지 값만을 갖는 AtomicBoolean에 비해 값을 변경할 수 있는 범위가 크기 때문에 그만큼 메소드도 다양하게 제공하고 있습니다.
AtomicInteger도 앞서 살펴본 compareAndSet(int expect, int update)과 getAndSet()을 파라미터나 리턴 타입만 바꿔서 동일하게 제공하고 있고, 이외에도 값을 Atomic하게 1씩 증가시키는 incrementAndGet(), x만큼 증가시키는 accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) 등이 있습니다.
어떤 메소드만 있는지 확인하면 사용 방법은 어렵지 않기 때문에 예제를 따로 다루진 않겠습니다.
(제공하는 메소드에 대한 자세한 내용은 javadoc을 참고해주세요.)
마무리
꼭 자바가 아니더라도 멀티 스레딩 환경에서 프로그래밍을 하게될 경우 가장 중요한 것은 아무래도 동기화 문제를 어떻게 해결할 것인가에 대한 처리라고 할 수 있습니다. 이번 포스팅에서 살펴본 Atomic Type도 그 방법 중 하나이고요.
그렇다면 어떤 상황에서든 이 Atomic Type을 사용하는 것이 능사일까요? 만약 그렇다면 volatile이나 synchronyzed 같은 방법은 존재하지 않았겠죠! 상황에 따라 Atomic Type을 사용할 수 없는 경우도, 혹은 비효율적인 경우도 있을 수 있기 때문에 모든 방법들을 다 숙지하여서 적재적소에 가장 효율적인 방법으로 프로그래밍 하면 되겠습니다.
'JAVA' 카테고리의 다른 글
Java에서 Scanner, System.out.println 보다 빠른 입출력 (0) | 2019.09.13 |
---|---|
[JAVA] Date to String / String to Date 변환(SimpleDateFormat) (0) | 2019.09.02 |
자바 스트림(Stream) 이해하기 (feat.JAVA 8) (0) | 2019.08.06 |
java.util.function 표준 API 파헤치기(feat.람다식) (0) | 2019.07.28 |
Java 가비지 컬렉터(GC) 이해하기 (1) | 2019.07.24 |