본문 바로가기
Web Development/Application

[JAVA] Atomic 변수와 동시성 제어 (AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference)

by saltyzun 2022. 9. 19.

얼마 전 코드에서 AtomicInteger 타입으로 선언된 변수를 발견했고, 관련 내용들을 찾아보게 되었다. 이번 글에서는 synchronized에 비해 적은 비용으로 동시성을 제어할 수 있는 Atomic 변수의 특성에 대해 알아보고자 한다.

 

목차

  • Atomic 변수란?
  • Java에서 동시성을 제어하기 위한 세 가지 방법
  • Atomic 변수의 장점 : Compare-and-swap(CAS)
  • Atomic  변수의 주요 메서드와 사용예시

 


 

Atomic 변수란?

Atomic 변수는 멀티쓰레드 환경에서 동시성을 보장해준다. 흔하게 사용되는 Atomic 변수 클래스로는 AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference 등이 있고, 이들은 atomic하게(동시성을 보장받으며) 업데이트 될 수 있는  int, long, boolean, object reference 를 각각 나타낸다. 

 


 

Java에서 동시성을 제어하기 위한 세 가지 방법

Java에서는 동시성을 제어할 필요가 있을 때 아래 세 가지 방법을 생각해볼 수 있다. 이 글에서는 Atomic 변수에 대해 다뤄볼 예정이고, 다른 방법들에 대해서는 시간이 되면 이어서 정리해보도록 하겠다. 

  • volatile
  • synchronized
  • Atomic variable(Atomic classes)

 


 

Atomic 변수의 장점 : Compare-and-swap(CAS)

앞서 살펴본 세가지의 동시성 제어 방식 중 Atomic 변수가 갖는 장점은 무엇일까? 가장 적은 비용으로 동시성을 제어할 수 있다는 점이다. 이는 다시 말해 Atomic 변수를 사용하는 것이 동시성을 제어하면서도 병행성을 가장 적게 희생시키는 방법이라는 뜻이다. 그렇다면 Atomic 변수는 어떠한 방식으로 동시성을 제어하고 있는지 살펴보자.

 

Compare-and-swap(CAS) 을 이용한 동시성 제어

 

 Atomic 변수는 내부적으로 CAS 방식을 사용해 동시성을 제어한다. CAS란 멀티쓰레드 환경에서 동시성을 보장하기 위한 컴퓨터 명령어(instruction)이다. 가령 하나의 쓰레드에서 동작하고 있는 프로그램이 특정 메모리에 접근하여 얻어온 값 A를 B로 업데이트 하는 상황을 생각해보자. CAS 명령어는 메모리 M에 위치한 값을 주어진 A값(up-to-date)과 비교하여 두 값이 일치할 때에만 메모리의 값을 B로 업데이트 하는데, 이 모든 과정이 하나의 atomic한 명령어 안에서 수행된다. 그리고 이러한 명령어의 원자성이 동시성을 보장해주게 된다.

💡 CAS 명령어는 3개의 피연산자로 구성된다. 
  1. 연산 대상이 되는 메모리의 위치 (M)
  2. 예상되는 변수의 현재 값 (A) 
  3. 셋팅되어야 하는 새로운 값 (B) 

 

✅  CAS는 다른 스레드의 접근을 막지 않는다

 

Java에서 동시성 제어를 위해 Atomic 변수를 사용하는 것이  lock 기반의 방식들(예. synchronized)를 사용하는 것에 비해 성능이 좋은 이유는 CAS 명령어는 값이 업데이트 되는 중에도 다른 스레드의 접근을 허용하기 때문이다. 즉, 하나의 스레드에서 특정 변수의 값을 업데이트 하고자 할 때 다른 쓰레드의 접근을 금지하는 lock기반의 방식들과 달리, CAS 를 이용할 경우 변수의 값을 업데이트 하기 위해 경합하던 쓰레드들은 그들이 값 없데이트에 성공했는지 여부(true/false)만 알 수 있을 뿐이다. 따라서 CAS를 이용해서 동시성을 제어하는 경우 쓰레드 들은 값을 업데이트 하기 위해 lock이 해제되길 기다리는 게 아니라 계속해서 그들의 작업을 수행할 수 있고, 이를 통해 Context-switching 이 발생하는 것을 피할 수 있게 된다. 

 


 

Atomic 변수의 주요 메서드와 사용 예시

  • get() : 메모리에서 값을 가져온다.
  • set() : 메모리에 값을 저장한다(쓰기).
  • lazySet() : 결국 메모리에 값을 쓰고, 이어지는 관련된 메모리 연산을 통해 순서를 바꿀 수 있다.
  • compareAndSet() : 앞에서 살펴본 CAS 연산과 같다. 값을 바꾸는 데 성공하면 true를 실패하면 false를 리턴한다. 
public class SafeCounterWithoutLock {
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public int getValue() {
        return counter.get();
    }
    public void increment() {
        while(true) {
            int existingValue = getValue();
            int newValue = existingValue + 1;
            if(counter.compareAndSet(existingValue, newValue)) {
                return;
            }
        }
    }
}

위 예시에서는 compareAndSet() 연산이 성공할 때까지 그 과정을 반복함으로써 increment() 메서드가 항상 counter의 값을 1씩 올려줄 수 있도록 보장하고 있다. 

 


 

마무리

이번 글에서는 Java에서 동시성 제어를 위해 사용할 수 있는 Atomic 변수에 대해 살펴보았다. Atomic 변수는 내부에서 CAS 명령어를 이용하므로 Lock 기반의 다른 방식들에 비해 Context-switching 비용을 줄일 수 있고, 덕분에 프로그램의 병행성을 높일 수 있다는 장점이 있다. 만약 동시성을 제어해야 하는 상황이 온다면 Atomic 변수를 이용해 그 비용을 최소화하는 방안을 생각해보자.

 

아울러 이번 글을 포스팅 하며 기본적인 CS 지식의 중요성을 다시금 깨닫는다. Computer Architecture 와 Operating System에 대한 기본적인 개념이 없었다면 Atomic 변수의 이점에 대해 이해하기 어려웠을 것이라 생각된다. 응용프로그램 개발자라 할지라도 CS 지식을 게을리 공부해서는 안 될 것 같다. 

 

Reference

 

Compare-and-swap - Wikipedia

In computer science, compare-and-swap (CAS) is an atomic instruction used in multithreading to achieve synchronization. It compares the contents of a memory location with a given value and, only if they are the same, modifies the contents of that memory lo

en.wikipedia.org

 

An Introduction to Atomic Variables in Java | Baeldung

Learn how to use atomic variables for solving concurrency issues.

www.baeldung.com

 

반응형

댓글