Compare And Swap (CAS) 개념 정리

devK08·2026년 1월 8일

1. CAS란?

Compare And Swap (비교 후 교환)

멀티스레드 환경에서 락(Lock) 없이 데이터를 안전하게 수정하는 원자적(Atomic) 연산입니다.

기본 원리

CAS(메모리 주소, 예상값, 새로운 값)

1. 메모리의 현재 값을 읽는다
2. 현재 값이 예상값과 같은가?
   - 같으면: 새로운 값으로 변경 → 성공
   - 다르면: 변경 안 함 → 실패

왜 필요한가?

// 멀티스레드 환경의 문제 (Race Condition)
int count = 0;
// Thread 1: count++  →  예상: count = 2
// Thread 2: count++  →  실제: count = 1 (덮어씀!)

Thread 1         Thread 2         count
-------------------------------------------
read (0)                           0
                 read (0)          0
write (1)                          1
                 write (1)         1  ← 덮어씀!

2. CAS 동작 방식

초기값: count = 5

Thread 1:                    Thread 2:
-------------------------------------------------
read → expected = 5
                             read → expected = 5
CAS(count, 5, 6) ✅          
  → count = 6
                             CAS(count, 5, 6) ❌
                               → 실패 (현재 6)
                             read → expected = 6
                             CAS(count, 6, 7) ✅
                               → count = 7

3. volatile 키워드

변수 가시성을 보장하는 키워드

private boolean flag = false;           // CPU 캐시 사용 → 가시성 문제
private volatile boolean flag = false;  // 메인 메모리 직접 접근 → 모든 스레드가 최신값 보장

왜 필요한가?

// volatile 없음
public class VisibilityProblem {
    private boolean flag = false;
    
    public void writer() { flag = true; }     // Thread 1
    public void reader() {                     // Thread 2
        while (!flag) { }  // 무한 루프! (캐시에서 계속 false 읽음)
    }
}

// volatile 있음
public class Solution {
    private volatile boolean flag = false;
    
    public void writer() { flag = true; }     // 메인 메모리에 즉시 반영
    public void reader() {                     
        while (!flag) { }  // 정상 종료 (메인 메모리에서 최신값 읽음)
    }
}

사용 예시

// ✅ 플래그, 상태 변수
public class Worker {
    private volatile boolean running = true;
    public void stop() { running = false; }
}

// ✅ Double-Checked Locking
private static volatile Singleton instance;

// ❌ 복합 연산 (count++는 원자적이지 않음)
private volatile int count = 0;
public void increment() { count++; }  // Race Condition 발생!

핵심: volatile은 가시성만 보장, 원자성은 보장 안 됨 → 복합 연산에는 AtomicInteger 사용


4. Java에서의 CAS

AtomicInteger 기본 사용

AtomicInteger count = new AtomicInteger(0);

// 증가/감소
count.incrementAndGet();  // ++count
count.getAndIncrement();  // count++
count.decrementAndGet();  // --count

// 덧셈
count.addAndGet(5);       // count += 5
count.getAndAdd(5);       // 현재 값 반환 후 += 5

// CAS
boolean success = count.compareAndSet(0, 1);

CAS 예제

public class CASExample {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        int expected, newValue;
        do {
            expected = count.get();
            newValue = expected + 1;
        } while (!count.compareAndSet(expected, newValue));
    }
}

compareAndSet vs compareAndExchange

핵심 차이

메서드반환값실패 시
compareAndSetboolean성공/실패만
compareAndExchange실제 값현재 값 반환

compareAndSet: 성공 여부만 필요할 때

// 반환: boolean
boolean success = count.compareAndSet(5, 10);
if (success) { /* ... */ }

// 재시도 패턴
do {
    expected = count.get();      // 매번 get() 호출!
    newValue = expected + 1;
} while (!count.compareAndSet(expected, newValue));

compareAndExchange: 재시도 로직에 효율적 (Java 9+)

// 반환: 실제 값
int actual = count.compareAndExchange(5, 10);
if (actual == 5) { /* 성공 */ }

// 재시도 패턴 (더 빠름!)
int expected = count.get();
while (true) {
    int actual = count.compareAndExchange(expected, expected + 1);
    if (actual == expected) break;
    expected = actual;  // get() 불필요! 30% 빠름
}

선택 기준:

단순 체크? → compareAndSet
재시도 루프? → compareAndExchange (더 빠름!)
Java 8? → compareAndSet만 가능

5. Lock vs CAS

비교

기준LockCAS
구현간단복잡
성능 (경쟁 적음)느림빠름 ⚡
성능 (경쟁 많음)안정적느려질 수 있음
Deadlock가능불가능
블로킹OX
적용 범위여러 변수단일 변수

Lock 방식

public class LockCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
}

// 특징: 블로킹, 컨텍스트 스위칭 오버헤드

CAS 방식

public class CASCounter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();
    }
}

// 특징: 락 없음, 빠름 (경쟁 적을 때)

성능 비교

벤치마크 (1000 스레드, 각 1000번)

Lock:        500ms
CAS:         150ms  ⚡ (3배 빠름)

6. CAS 장단점 & ABA 문제

✅ 장점

1. Lock-Free
   - 스레드 블로킹 없음
   - Deadlock 불가능

2. 빠름 (경쟁 적을 때)
   - 단일 CPU 명령어 수준

❌ 단점

1. 재시도 오버헤드 (경쟁 많을 때)
   - 100개 스레드 → 99개 실패 → 재시도 반복

2. ABA 문제
   - ABA 변경 시 감지 못함

3. 단일 변수만 가능
   - 여러 변수 동시 변경 불가

ABA 문제

// 문제 상황
초기: count = 5

Thread 1:
  1. read → expected = 5
  2. [대기]

Thread 2:
  3. count: 53
  4. count: 35  (원래대로)

Thread 1:
  5. CAS(5, 6) → 성공!
     하지만 "다른 5"인데 성공함

해결: AtomicStampedReference (버전 추가)

AtomicStampedReference<Node> top = 
    new AtomicStampedReference<>(null, 0);

// 값과 버전 함께 비교
if (top.compareAndSet(oldNode, newNode, oldStamp, oldStamp + 1)) {
    // 버전까지 일치해야 성공
}

7. Atomic 클래스 종류

기본 타입

AtomicInteger count = new AtomicInteger(0);
AtomicLong longCount = new AtomicLong(0L);
AtomicBoolean flag = new AtomicBoolean(false);

참조 타입

// 단일 참조
AtomicReference<User> userRef = new AtomicReference<>();

// 버전 포함 (ABA 해결)
AtomicStampedReference<User> stampedRef = 
    new AtomicStampedReference<>(user, 0);

배열

AtomicIntegerArray array = new AtomicIntegerArray(10);
array.incrementAndGet(0);  // array[0]++

AtomicReferenceArray<User> userArray = 
    new AtomicReferenceArray<>(10);

LongAdder

AtomicInteger/AtomicLong: 단일 변수에 CAS 적용

AtomicLong counter = new AtomicLong(0);

// 모든 스레드가 하나의 변수를 두고 경쟁
Thread 1: CAS(0, 1) → 성공
Thread 2: CAS(0, 1) → 실패 (이미 1) → 재시도
Thread 3: CAS(0, 1) → 실패 (이미 1) → 재시도
// 경쟁 많을수록 재시도 증가 → 느려짐

LongAdder: 여러 셀로 분산하여 경쟁 감소 (Java 8+)

LongAdder adder = new LongAdder();

// 동작 방식 (Adaptive)
1. 경쟁 없음: base 변수만 사용 (0)
2. 경쟁 발생: Cell[2] 생성 (2)
3. 경쟁 심화: Cell[4] 확장 (4)
4. 더 심화:   Cell[8] 확장 (8)
   ...
   최대: CPU 코어 수까지 확장

// 왜 CPU 코어 수까지만?
CPU 4코어인 경우:
- 동시 실행 가능한 Thread = 최대 4-4개면 충분 (Thread가 다른 셀 사용)
-8개로 늘려도 의미 없음 (Thread4개만 동시 실행)

// Thread는 해시로 셀 선택
Thread 1hash(ThreadID) % 셀개수 → Cell[0]
Thread 2hash(ThreadID) % 셀개수 → Cell[1]
Thread 3hash(ThreadID) % 셀개수 → Cell[2]

// 예시: CPU 4코어, 셀 4개
base = 2
Cell[0] = 3Thread 1, 5, 9, ...
Cell[1] = 5Thread 2, 6, 10, ...
Cell[2] = 4Thread 3, 7, 11, ...
Cell[3] = 1Thread 4, 8, 12, ...

// 읽기: base + 모든 셀 합산
long sum = adder.sum();  // 2 + 3 + 5 + 4 + 1 = 15

핵심:

  • Thread ID 해시로 셀 분산
  • 경쟁 심하면 셀 개수 자동 확장 (Adaptive!)
  • CPU 코어 수 = 동시 실행 Thread 수 → 그 이상은 불필요
CPU 4코어:
동시 실행: [T1] [T2] [T3] [T4]
셀 4개:    [C0] [C1] [C2] [C3]  ← 딱 맞음!

셀 8개로 늘려도?
동시 실행: [T1] [T2] [T3] [T4]  ← 여전히 4개만
셀 8개:    [C0] [C1] [C2] [C3] [C4] [C5] [C6] [C7]
                                  ↑ 안 쓰임 (메모리 낭비)

비교

특징AtomicLongLongAdder
구조단일 변수여러 셀 배열
쓰기 (경쟁 적음)빠름비슷
쓰기 (경쟁 많음)느림빠름 ⚡
읽기빠름느림 (합산 필요)
메모리적음많음
사용자주 읽기자주 쓰기

언제 사용?

// ✅ AtomicLong 사용
- 읽기가 많음 (빠른 get())
- 스레드 수 적음
- 메모리 제약

// ✅ LongAdder 사용
- 쓰기가 많음 (카운터, 통계)
- 스레드 수 많음 (높은 경쟁)
- 읽기는 가끔만 (배치로)

// 예시
public class Metrics {
    private LongAdder requestCount = new LongAdder();
    
    public void recordRequest() {
        requestCount.increment();  // 빠름!
    }
    
    public long getTotal() {
        return requestCount.sum();  // 가끔만 호출
    }
}

8. 최적화 & 선택 가이드

최적화 팁

Tip 1: LongAdder 사용

// 경쟁 많을 때
LongAdder counter = new LongAdder();  // 여러 셀로 분산

Tip 2: 읽기 먼저

// 빠른 읽기로 필터링
if (!flag.get() && flag.compareAndSet(false, true)) {
    // ...
}

Tip 3: Backoff 전략

// 재시도 시 CPU 양보
if (retries > 0) {
    Thread.yield();
}

선택 가이드

✅ CAS 사용

- 단일 변수 (카운터, 플래그)
- 간단한 연산 (++, get/set)
- 경쟁 적음
- 높은 처리량 필요

✅ Lock 사용

- 여러 변수 동시 변경
- 복잡한 비즈니스 로직
- 경쟁 많음
- 순서 보장 필요

핵심 요약

CAS

원리: 예상값 == 현재값 → 변경 (원자적)
Java: AtomicInteger, AtomicLong, AtomicReference
장점: 빠름, Lock-Free, Deadlock 없음
단점: 재시도 오버헤드, ABA 문제, 단일 변수만

volatile

역할: 가시성 + 재배치 방지
한계: 원자성 보장 안 됨 (count++ 불가)
사용: 플래그, 상태 변수
결합: volatile + CAS = Atomic

compareAndSet vs compareAndExchange

Set: boolean 반환, 단순 체크
Exchange: 실제 값 반환, 재시도 30% 빠름 (Java 9+)

ABA 문제

문제: A → B → A 변경 감지 못함
해결: AtomicStampedReference (버전 추가)

Lock vs CAS

CAS: 단일 변수, 경쟁 적음 → 빠름
Lock: 여러 변수, 경쟁 많음 → 안정적

실전

카운터: AtomicInteger, LongAdder
플래그: AtomicBoolean
참조: AtomicReference
고성능: LongAdder (경쟁 많을 때)

profile
안녕하세요. 개발자 지망 고등학생입니다.

0개의 댓글