0Pricing
Java Academy · 강의

원자적 변수: 잠금 없는 업데이트

잠금 없이 스레드 안전한 카운터를 구현하도록 AtomicInteger, AtomicLong, AtomicReference를 사용합니다.

원자적 변수: 잠금 없는 업데이트은(는) CoddyKit의 무료 Java Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Java Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

원자적이지 않은 작업의 문제

단순한 증가 연산 count++은 스레드 안전하지 않습니다. 읽기, 더하기, 쓰기라는 세 작업으로 이루어져 있기 때문입니다. 동시에 실행되는 스레드가 이 작업 사이에 끼어들 수 있어 업데이트가 유실됩니다. 원자 변수를 사용하면 잠금 없이 이 문제를 해결할 수 있습니다.

// UNSAFE:
int count = 0;
// Thread A reads count=0, Thread B reads count=0
// Both write 1 — second update lost!
count++; // NOT thread-safe

// SAFE:
import java.util.concurrent.atomic.*;
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet(); // atomic, lock-free

AtomicInteger 기초

AtomicInteger는 CPU의 비교 후 교환(CAS) 명령을 사용해 원자성이 보장되는 정수 작업을 제공합니다. 잠금이 필요하지 않습니다.

AtomicInteger counter = new AtomicInteger(0);

counter.set(10);                   // set to 10
System.out.println(counter.get()); // 10

int prev = counter.getAndIncrement(); // returns 10, sets to 11
int curr = counter.incrementAndGet(); // sets to 12, returns 12
counter.addAndGet(5);                 // adds 5, returns 17
counter.decrementAndGet();            // subtracts 1, returns 16

compareAndSet: CAS 작업

compareAndSet(expected, update)는 현재 값이 예상 값과 같을 때만 값을 원자적으로 업데이트합니다. 성공하면 true를 반환합니다. 이는 잠금 없는 모든 알고리즘의 기반입니다.

AtomicInteger ai = new AtomicInteger(5);

boolean updated = ai.compareAndSet(5, 10); // current==5? set to 10
System.out.println(updated); // true
System.out.println(ai.get()); // 10

boolean failed = ai.compareAndSet(5, 20); // current==10, not 5 → fails
System.out.println(failed); // false
System.out.println(ai.get()); // still 10

카운터를 위한 AtomicLong

AtomicLong은 64비트에 해당하는 타입입니다. 시퀀스 생성기와 적중 횟수 카운터에 흔히 사용됩니다:

AtomicLong seq = new AtomicLong(0);

// Thread-safe sequence generator:
long nextId() {
    return seq.incrementAndGet();
}

System.out.println(nextId()); // 1
System.out.println(nextId()); // 2
System.out.println(nextId()); // 3

AtomicBoolean

스레드 안전한 불리언 플래그입니다. 한 번만 수행하는 초기화나 종료 신호에 흔히 사용됩니다:

AtomicBoolean initialized = new AtomicBoolean(false);

void initOnce() {
    // compareAndSet: only first caller succeeds
    if (initialized.compareAndSet(false, true)) {
        System.out.println("Initializing...");
        // perform expensive init
    } else {
        System.out.println("Already initialized");
    }
}

AtomicReference

AtomicReference<V>는 객체 참조를 원자적으로 업데이트합니다. 잠금 없는 자료 구조를 구현하는 데 사용됩니다:

import java.util.concurrent.atomic.*;

record Config(String host, int port) {}

AtomicReference<Config> configRef =
    new AtomicReference<>(new Config("localhost", 8080));

// Hot-swap the configuration atomically:
Config oldConfig = configRef.get();
Config newConfig = new Config("prod.example.com", 443);

boolean swapped = configRef.compareAndSet(oldConfig, newConfig);
System.out.println(swapped); // true
System.out.println(configRef.get()); // Config[host=prod.example.com, port=443]

updateAndGet / getAndUpdate

내부적으로 CAS 재시도 루프를 사용해 함수를 원자적으로 적용합니다:

AtomicInteger value = new AtomicInteger(10);

// Apply function atomically: square the value
int newVal = value.updateAndGet(x -> x * x);
System.out.println(newVal); // 100

// Get old, then apply:
int old = value.getAndUpdate(x -> x + 1);
System.out.println(old);   // 100
System.out.println(value.get()); // 101

accumulateAndGet

함수를 사용해 현재 값과 지정된 값을 결합합니다:

AtomicInteger max = new AtomicInteger(0);

// Keep running maximum:
max.accumulateAndGet(42, Math::max); // max(0, 42) = 42
max.accumulateAndGet(15, Math::max); // max(42, 15) = 42
max.accumulateAndGet(99, Math::max); // max(42, 99) = 99

System.out.println(max.get()); // 99

경합이 높은 카운터를 위한 LongAdder

스레드 경합이 심한 상황에서는 AtomicLong의 CAS 재시도로 인해 성능이 저하될 수 있습니다. LongAdder는 여러 셀을 사용해 경합을 줄입니다:

import java.util.concurrent.atomic.*;

LongAdder adder = new LongAdder();

// Multiple threads can call add() with minimal contention:
adder.increment();
adder.add(5);
adder.increment();

System.out.println(adder.sum()); // 7
// Note: sum() is NOT atomic with add() — use only when no concurrent adds

원자 배열

AtomicIntegerArray와 AtomicLongArray는 배열의 개별 요소에 원자적 작업을 제공합니다:

AtomicIntegerArray arr = new AtomicIntegerArray(5);

arr.set(0, 10);
arr.incrementAndGet(0);
arr.compareAndSet(2, 0, 42); // index 2: if 0, set 42

System.out.println(arr.get(0)); // 11
System.out.println(arr.get(2)); // 42

원자 변수와 잠금 중 무엇을 사용할까

다음과 같은 경우 원자 변수를 사용합니다:

  • 단일 변수 업데이트(카운터, 플래그, 참조)
  • 경합이 적은 상황
  • 잠금 없는 성능이 중요할 때

다음과 같은 경우 잠금을 사용합니다:

  • 여러 변수를 원자적으로 업데이트해야 할 때
  • 복잡한 조건부 논리가 필요할 때
  • 경합이 매우 심할 때(CAS 재시도로 성능이 저하됨)

빠른 확인

compareAndSet(expected, update)는 무엇을 보장합니까?

다시 보기: 원자 변수

핵심 정리:

  • 원자 타입은 CPU CAS 명령을 사용하므로 잠금 없이 스레드 안전하게 동작함
  • 단일 변수의 원자성에는 AtomicInteger/Long/Boolean/Reference 사용
  • compareAndSet은 잠금 없는 알고리즘의 기반
  • 함수형 업데이트에는 updateAndGet/accumulateAndGet 사용
  • 경합이 심한 카운터 상황에는 LongAdder 사용

자주 묻는 질문

“원자적 변수: 잠금 없는 업데이트” 강의는 무료인가요?

네 — “원자적 변수: 잠금 없는 업데이트” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Java Academy 강의 전체를 잠금 해제할 수 있습니다. Java Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“원자적 변수: 잠금 없는 업데이트”에서 뭘 배우나요?

잠금 없이 스레드 안전한 카운터를 구현하도록 AtomicInteger, AtomicLong, AtomicReference를 사용합니다. 브라우저에서 직접 실행하는 실습 코드로 Java Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Java Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Java Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“원자적 변수: 잠금 없는 업데이트” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Java Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Java Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. ReentrantLock과 synchronized 비교
  2. 리더-라이터 상황을 위한 ReadWriteLock
  3. 원자적 변수: 잠금 없는 업데이트
  4. StampedLock과 낙관적 읽기
← Java Academy(으)로 돌아가기