CAS(Compare And Swap)

appti·2024년 2월 29일
0

분석

목록 보기
4/23

서론

자바에서 CAS 연산은 비동기 작업을 지원하는 여러 클래스에서 사용하는 동기화 기법입니다.

그렇기 때문에 여러 비동기 지원 클래스를 이해하기 위해서 반드시 학습해야 할 개념이기 때문에, 한 번 정리하면서 학습해보고자 합니다.

CAS (Compare And Swap)

CAS란 동기화 기법 중 하나입니다.
멀티 스레드 간의 Non-Blocking 기반으로 경쟁 조건과 데드락을 방지할 수 있습니다.

위와 같이 현재 CPU에 저장된 값(= 기존 값)과 스레드에 저장된 값(= 비교 기준 값)을 비교하는 식으로 진행합니다.

동작 방식

스레드 T1, T2가 있고, 동시에 특정 값에 접근해 CAS로 값을 변경하고자 한다고 가정하겠습니다.
이 때 CPU에 저장된 값은 5입니다.

T1이 접근해서 기존 값과 비교 기준 값을 비교합니다.

서로 5이므로 일치합니다.
그러므로 기존 값을 변경 값인 7로 변경합니다.

이후 T2가 접근해서 기존 값과 비교 기준 값을 비교합니다.

5 != 7이기 때문에 일치하지 않습니다.
그러므로 변경 값인 8로 변경하지 못하고 기존 값은 그대로 7로 유지합니다.

이러한 CAS 연산은 CPU 연산이 원자적인 특징을 이용한 것이며, 값의 조회/비교/변경의 과정을 하나의 원자적 연산으로 처리한 것입니다.

이는 값을 비교하는 과정에서 드러나게 됩니다.

기존 값이 비교 기준 값이 다르다면 연산을 수행하기 전 다른 스레드가 이미 연산을 수행한 것이며, 이는 원자적으로 실행하지 못한다는 상황이기 때문에 실패하게 됩니다.
기존 값과 비교 기준 값이 일치한다면 연산을 원자적으로 실행할 수 있다는 것이기 때문에 성공하게 됩니다.

이로 인해 다른 스레드를 대기시킬 필요가 없으므로 논-블로킹 방식으로 수행이 가능한 것입니다.

전제 조건

CAS 연산은 원자성을 보장하지만, 이 원자성을 보장하기 위해서는 CPU cache가 아닌 CPU를 바라봐야 합니다.
즉, 가시성이 보장되어야 합니다.

Atomic

자바에서 CAS 연산을 지원해주는 클래스는 java.util.concurrent.atomic에 위치하고 있습니다.

이 중 CAS 연산을 수행하고 있는 클래스 일부를 확인해보고자 합니다.

AtomicInteger

AtomicInteger는 이름 그대로 Integer에 대한 원자적 연산이 가능하도록 지원하는 기능을 제공합니다.

API 중 AtomicInteger.incrementAndGet()이 CAS 연산을 구현하고 있습니다.

저수준 레벨에 접근해야 하므로 Unsafe.getAndAddInt()를 호출합니다.

getIntVolatile()을 통해 가시성을 확보한 채로 비교 기준 값을 조회합니다.
weakCompareAndSetInt()를 통해 기존 값과 비교 기준 값을 비교하고, 일치한다면 값을 세팅한 뒤, 그 결과를 반환합니다.

최종적으로 native 메서드를 호출합니다.
설명에서 언급한 대로, 비교 기준 값을 보유하고 있는 경우 해당 변수를 x로 원자적으로 업데이트 합니다.

이를 통해 AtomicInteger.incrementAndGet()은 원자적으로 저장한 값을 1 증가시키기 위해 CAS 연산을 수행하는 것을 확인할 수 있습니다.

AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater는 지정한 필드를 리플렉션을 활용해 원자적 연산을 제공하는 기능입니다.

AtomicInteger와 동일하게 Unsafe.compareAndSetInt()를 호출해 CAS 연산을 수행하고 있음을 확인할 수 있습니다.

사용 예시

Future

Future는 비동기 작업의 결과를 알려주는데 사용하는 인터페이스입니다.

위 코드는 Future의 구현체인 FutureTask이며, 현재 상태를 체크하기 위해 CAS 연산을 수행합니다.

ConcurrentHashMap

ConcurrentHashMap은 동시성 문제를 해결할 수 있는 대표적인 자료구조입니다.

특정 키에 대한 값의 업데이트나 삭제, 새로운 key-value 삽입 등의 API에 대한 안전한 수행을 위해 CAS 연산을 사용합니다.

synchronized와 비교

CAS 연산과 가장 자주 비교되는 방식은 synchronized 입니다.

Blocking & Non-Blocking

synchronized는 블로킹 방식이며, CAS 연산은 논-블로킹 방식입니다.

synchronized는 스레드가 대기하고, 다른 스레드로 전환하는 컨텍스트 스위칭이 발생하기 때문에 이런 관점에서의 비용은 CAS 연산이 더 효율적입니다.

락 획득 방식

CAS 연산은 스핀 락 방식입니다. 즉, 락을 획득할 때 까지 무한히 시도합니다.
이는 상황에 따라 성능에 큰 영향을 끼칠 수 있습니다.

예를 들어, 매우 자주 변경되는 공유 데이터에 스레드 100개가 접근해서 변경해야 한다면, 그리고 변경하는 작업이 오래 걸린다면 성능이 기하급수적으로 떨어지게 됩니다.

synchronized는 블로킹 락을 사용하므로 위와 같은 환경에서는 비교적 효율적인 성능을 가질 수 있습니다.

결론

  • CAS 연산은 Non-Blocking 방식으로 데드 락이나 경쟁 조건을 회피할 수 있게 해 주는 동기화 기법 중에 하나입니다.
  • 값 조회/비교/변경을 하나의 원자적 연산으로 처리합니다.
  • 자바에서 다양한 비동기 지원 클래스에 사용됩니다.
  • CAS 연산은 스핀 락을 사용하기 때문에 동시에 접근하는 스레드가 많거나, 작업이 오래 걸리는 경우에는 성능이 저하될 수 있습니다.
profile
안녕하세요

0개의 댓글