개발/Java

Atomic Reference란?

nova_dev 2025. 3. 18. 23:19
반응형

1. AtomicReference란?

안녕하세요! 오늘은 멀티스레드 환경에서 안전하게 객체 참조(Reference)를 관리하는 방법 중 하나인 AtomicReference에 대해 알아보려고 합니다.

Java에서 멀티스레드 프로그래밍을 하다 보면, 여러 스레드가 동시에 객체의 상태를 변경할 수 있기 때문에 동기화 문제가 발생할 수 있습니다. 일반적으로 synchronized 키워드를 사용하거나, volatile을 활용하는 방법이 있지만, AtomicReference를 사용하면 락을 사용하지 않고도 객체의 참조를 안전하게 변경할 수 있습니다.

그럼 AtomicReference가 무엇인지, 어떻게 활용할 수 있는지 함께 살펴볼까요? 🚀

 

📌 왜 필요할까?

일반적으로 volatile 키워드를 사용하면 읽기와 쓰기는 보장되지만, 여러 단계의 연산이 필요한 경우에는 원자성이 보장되지 않습니다.

예를 들어, 아래 코드를 보면 문제를 확인할 수 있습니다.

class SharedObject {
    volatile String value = "A";
}

public class VolatileIssueExample {
    public static void main(String[] args) {
        SharedObject shared = new SharedObject();

        new Thread(() -> {
            if (shared.value.equals("A")) {  // 1. 값을 읽음
                shared.value = "B";         // 2. 값을 변경
            }
        }).start();

        new Thread(() -> {
            if (shared.value.equals("A")) {  // 3. 다른 스레드도 같은 값 읽음
                shared.value = "C";         // 4. 값을 변경
            }
        }).start();
    }
}

위 코드의 문제점:

  • 두 스레드가 shared.value의 값을 동시에 읽은 후 변경하면, 한 스레드의 업데이트가 덮어씌워질 가능성이 있습니다.
  • 즉, "A" → "B"로 변경한 값이 다른 스레드에 의해 "C"로 덮어씌워질 수 있음
  • 이 문제를 해결하려면 synchronized를 사용해야 하지만, 성능 저하가 발생할 수 있습니다.

이때 AtomicReference를 사용하면 CAS(Compare-And-Swap) 연산을 활용하여 안전하게 업데이트할 수 있습니다.

AtomicReference<T>는 객체 참조(레퍼런스)를 원자적으로(Atomic) 업데이트할 수 있도록 지원하는 클래스입니다.

즉, 멀티스레드 환경에서 객체의 동기화 문제를 해결할 때 사용됩니다.

2. AtomicReference 주요 특징

CAS (Compare-And-Swap) 연산 사용 → 동기화 없이 안전한 업데이트

객체의 불변성이 필요 없을 때 유용

멀티스레드 환경에서 안전한 공유 객체 관리 가능

락(lock) 없이 상태 변경 가능 (성능 향상)


3. AtomicReference 사용법

1️⃣ 기본적인 사용법 (get, set)

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    public static void main(String[] args) {
        AtomicReference<String> ref = new AtomicReference<>("초기값");

        System.out.println("현재 값: " + ref.get()); // 초기값

        ref.set("새로운 값");
        System.out.println("변경된 값: " + ref.get()); // 새로운 값
    }
}

💡 get() → 현재 값 가져오기

💡 set(value) → 새로운 값 설정


2️⃣ compareAndSet() 사용 (CAS 연산)

비교 후 조건이 맞으면 변경하는 방식

import java.util.concurrent.atomic.AtomicReference;

public class CompareAndSetExample {
    public static void main(String[] args) {
        AtomicReference<String> ref = new AtomicReference<>("A");

        boolean success = ref.compareAndSet("A", "B");
        System.out.println("변경 성공 여부: " + success); // true
        System.out.println("현재 값: " + ref.get()); // B

        success = ref.compareAndSet("A", "C"); // 현재 값은 B라서 변경되지 않음
        System.out.println("변경 성공 여부: " + success); // false
        System.out.println("현재 값: " + ref.get()); // B
    }
}

✅ compareAndSet(expected, newValue)

  • 현재 값이 expected와 같다면 newValue로 변경
  • 다르면 변경하지 않음 (스레드 충돌 방지 가능)

3️⃣ updateAndGet() (람다를 이용한 값 업데이트)

java
복사편집
import java.util.concurrent.atomic.AtomicReference;

public class UpdateAndGetExample {
    public static void main(String[] args) {
        AtomicReference<Integer> ref = new AtomicReference<>(10);

        // 현재 값에 +1을 적용한 새 값으로 원자적으로 업데이트
        int newValue = ref.updateAndGet(value -> value + 1);

        System.out.println("새로운 값: " + newValue); // 11
    }
}

✅ updateAndGet() → 기존 값을 기반으로 새 값을 계산하여 원자적으로 변경


🚀 AtomicReference vs volatile vs synchronized

기법 특징 장점 단점

volatile 단일 변수 읽기/쓰기 보장 빠름 여러 단계 연산에서는 원자성 보장 X
synchronized 블록 또는 메서드에 락 적용 정확한 동기화 보장 성능 저하 (락으로 인한 병목)
AtomicReference 객체 레퍼런스 원자적 변경 락 없이 동기화 가능 (CAS) 객체 필드 업데이트 시 복잡할 수 있음

📌 언제 AtomicReference를 사용해야 할까?

멀티스레드 환경에서 객체 레퍼런스를 안전하게 변경하고 싶을 때

락(synchronized)을 사용하지 않고 동시성을 보장하고 싶을 때

불변 객체가 아닌 변경 가능한 객체를 안전하게 공유해야 할 때

 

🚀 정리:

  • AtomicReference는 객체 레퍼런스를 원자적으로 변경할 때 사용
  • compareAndSet()을 이용한 CAS 연산으로 안전한 업데이트 가능
  • volatile보다 여러 단계의 변경 작업을 안전하게 수행 가능
  • 락을 사용하지 않아 성능이 더 좋음
반응형