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보다 여러 단계의 변경 작업을 안전하게 수행 가능
- 락을 사용하지 않아 성능이 더 좋음
'개발 > Java' 카테고리의 다른 글
Java의 @NonNull vs @Nonnull vs @NotNull 정리 (1) | 2025.03.20 |
---|---|
Java 14 Record Keyword (0) | 2024.11.22 |
[Lombok][Warning] @Builder will ignore the initializing expression entirely. (0) | 2023.01.15 |
[Lombok][Warning] 상속받은 클래스에 @Data만 사용하면 Warning이 뜨는 이유 (0) | 2023.01.14 |
[Java] AssertJ 사용법 (0) | 2022.10.23 |