JAVA

태태개발일지 - 자바 고급 (김영한) CAS 연산

태태코 2025. 3. 17. 14:45
반응형

자바 원자적 연산

원자적 연산이란?

하나의 연산이 중간에 끊기지 않고 한 번에 수행되는 것

 

 

원자적 연산을 사용하는 이유

멀티스레드 환경에서 동시에 여러 스레드가 공유 변수에 접근하면 경합이 발생할 수 있다.

이 문제를 해결하기 위해 synchronized 와 같은 키워드로 lock을 걸어서 동기화 할 수 있지만, 락을 사용하면 성능이 저하될 수 있다.

 

ex) 아래는 원자적인 연산이 아니여서 경합이 발생할 수 있는 코드이다.

public void use(){

if(i==10){



}

i++;

}

CAS

CAS(Compare-And-Swap) 을 사용해서 동기화 없이(비블로킹, Lock-Free) 원자적 연산을 수행할 수 있도록 설계된 클래스

 

 

사용법

 

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(0);

        // 값 증가
        System.out.println(atomicInt.incrementAndGet()); // 1

        // 값 감소
        System.out.println(atomicInt.decrementAndGet()); // 0

        // 특정 값 더하기
        System.out.println(atomicInt.addAndGet(5)); // 5

        // 현재 값이 예상한 값(5)과 같으면 10으로 변경
        boolean success = atomicInt.compareAndSet(5, 10);
        System.out.println(success + " " + atomicInt.get()); // true 10
    }
}

 

핵심 메서드

compareAndSet(expectedValue, newValue) 연산은 다음과 같은 원리로 동작한다.
  1. 현재 변수의 값이 expectedValue(기대한 값)과 같은지 확인한다.
  2. 같으면 새로운 값(newValue)으로 변경한다.
  3. 다르면 아무 작업도 하지 않고 실패한다.

장점과 단점

장점

  • 락을 사용하지 않으므로 성능이 좋음 (락 기반 동기화보다 빠름)
  • 데드락(Deadlock) 문제 없음
  • 멀티스레드 환경에서도 안전하게 동작

단점

  • CAS 실패 시 반복(Spin Loop)가 필요해서, 경쟁이 심한 경우 오히려 성능이 저하될 수 있음
  • 한 번에 하나의 변수만 갱신 가능 (여러 변수를 동시에 갱신하려면 AtomicReference 또는 StampedLock 같은 방법이 필요)

 

CAS 실패 시 반복(Spin Loop)가 필요해서, 경쟁이 심한 경우 오히려 성능이 저하될 수 있음에 대해서 더 알아보자면,

 

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    static AtomicInteger atomicInt = new AtomicInteger(0);

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (!atomicInt.compareAndSet(0, 1)) {
                // 실패하면 계속 시도 (Busy-waiting)
            }
            System.out.println("Thread 1: 값 변경 완료");
        });

        Thread t2 = new Thread(() -> {
            while (!atomicInt.compareAndSet(0, 2)) {
                // 실패하면 계속 시도 (Busy-waiting)
            }
            System.out.println("Thread 2: 값 변경 완료");
        });

        t1.start();
        t2.start();
    }
}

 

아래와 같이 스핀락이 계속 발생하기 때문에 대기시간이 길수록 성능이 안좋다는 이슈를 가지고 있다.

 

반응형