๐Ÿ“Œ volatile๊ณผ CAS(Compare-And-Swap): ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

์„ฑ์ข…ํ˜ธยท2025๋…„ 2์›” 1์ผ
1

๐Ÿ“Œ CAS(Compare-And-Swap)์™€ volatile: ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

synchronized ๊ฐ™์€ ๋ฝ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹๋„ ์žˆ์ง€๋งŒ, ์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•˜๋ฉด ๋น„์ฐจ๋‹จ ๋™๊ธฐํ™”(Non-blocking Synchronization) ๊ธฐ๋ฒ•์ด ๋”์šฑ ์ ํ•ฉํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ €๋ฒˆ ๋ธ”๋กœ๊น…์—์„œ ๋™์‹œ์„ฑ์„ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜๋Š”๋ฐ Java๋ฅผ ์ด์šฉํ•œ ๋™์‹œ์„ฑ ์ œ์–ด ๋ฐฉ์‹์— CAS ์—ฐ์‚ฐ์ด ์งง๊ฒŒ ๋‚˜์™”์–ด์„œ CAS ์—ฐ์‚ฐ๊ณผ ์ถ”๊ฐ€๋กœ volatile์„ ์ด์šฉํ•œ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ… ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” CAS(Compare-And-Swap)์™€ volatile์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃจ๊ณ , ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ๊ฒ€์ฆํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ „์ฒด ์ฝ”๋“œ๋Š” ๊นƒํ—™๋ ˆํฌ์—์„œ ํ™•์ธํ•ด์ฃผ์„ธ์š”


1๏ธโƒฃ volatile์ด๋ž€?

1.1 volatile์˜ ์ •์˜

volatile์€ JVM์ด ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ์ง์ ‘ ๋ฉ”์ธ ๋ฉ”๋ชจ๋ฆฌ(Main Memory)์—์„œ ์ฝ๊ณ  ์“ฐ๋„๋ก ๋ณด์žฅํ•˜๋Š” ํ‚ค์›Œ๋“œ์ด๋‹ค.
์ฆ‰, ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ํ•ญ์ƒ ์ตœ์‹  ๊ฐ’์„ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€์‹œ์„ฑ(visibility)์„ ์ œ๊ณตํ•œ๋‹ค.

private volatile int count = 0;

1.2 volatile์˜ ํŠน์ง•

โœ… ๊ฐ€์‹œ์„ฑ(Visibility) ๋ณด์žฅ: ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์ผํ•œ ๊ฐ’์„ ๋ณด์žฅ๋ฐ›์Œ
โŒ ์›์ž์„ฑ(Atomicity) ๋ฏธ๋ณด์žฅ: count++ ๊ฐ™์€ ์—ฐ์‚ฐ์€ ์—ฌ์ „ํžˆ ์•ˆ์ „ํ•˜์ง€ ์•Š์Œ
โŒ ๋™๊ธฐํ™”(Synchronization) ๋ฏธ๋ณด์žฅ: ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์ •ํ•ฉ์„ฑ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์Œ


2๏ธโƒฃ CAS(Compare-And-Swap)๋ž€?

2.1 CAS์˜ ์ •์˜

CAS๋Š” ์›์ž์ ์œผ๋กœ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ์—ฐ์‚ฐ์ด๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ณผ์ •์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

  1. ํ˜„์žฌ ๊ฐ’(Old Value)์„ ์ฝ์–ด์˜จ๋‹ค.
  2. ์˜ˆ์ƒ ๊ฐ’(Old Value)์ด ์—ฌ์ „ํžˆ ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
  3. ๋งž์œผ๋ฉด ์ƒˆ๋กœ์šด ๊ฐ’(New Value)์œผ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.
  4. ๋งŒ์•ฝ ๋ณ€๊ฒฝ ์ค‘ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ’์„ ๋ฐ”๊ฟจ๋‹ค๋ฉด, ๋ณ€๊ฒฝ์— ์‹คํŒจํ•˜๊ณ  ๋‹ค์‹œ ์‹œ๋„ํ•œ๋‹ค.

2.2 CAS์˜ ํŠน์ง•

โœ… ์›์ž์„ฑ(Atomicity) ๋ณด์žฅ: CAS ์—ฐ์‚ฐ ์ž์ฒด๊ฐ€ ์›์ž์ ์ด๋ฏ€๋กœ ๋ฝ ์—†์ด ๋™์‹œ์„ฑ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
โŒ ๊ฐ€์‹œ์„ฑ(Visibility) ๋ฏธ๋ณด์žฅ: ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— CAS๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ์ตœ์‹  ๊ฐ’์„ ๋ณด์žฅํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Œ
โŒ ABA ๋ฌธ์ œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ: ์˜ˆ์ƒ ๊ฐ’์ด ๋ฐ”๋€Œ์—ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ๋Œ์•„์˜จ ๊ฒฝ์šฐ์—๋„ CAS๊ฐ€ ์„ฑ๊ณตํ•  ์ˆ˜ ์žˆ์Œ


3๏ธโƒฃ Unsafe๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ CAS ๊ตฌํ˜„ํ•˜๊ธฐ

CAS ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด JVM ๋‚ด๋ถ€์—์„œ ์ œ๊ณตํ•˜๋Š” Unsafe ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
ํ•˜์ง€๋งŒ Unsafe.getUnsafe()๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฉด SecurityException์ด ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์—,
๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ Unsafe ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” UnsafeUtil์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

3.1 UnsafeUtil: ๋ฆฌํ”Œ๋ ‰์…˜์„ ์ด์šฉํ•œ Unsafe ๊ฐ์ฒด ์ ‘๊ทผ

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeUtil {
    private UnsafeUtil() {
        // ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋Š” ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐฉ์ง€
    }

    public static Unsafe getUnsafe() {
        try {
            // Unsafe ํด๋ž˜์Šค์˜ theUnsafe ํ•„๋“œ๋ฅผ ๊ฐ€์ ธ์˜ด (์ด ํ•„๋“œ๋Š” private static final ๋ณ€์ˆ˜)
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true); // ์ ‘๊ทผ ๊ถŒํ•œ ํ•ด์ œ
            return (Unsafe) field.get(null); // ํ•„๋“œ ๊ฐ’์„ ๊ฐ€์ ธ์™€ Unsafe ๊ฐ์ฒด๋กœ ์บ์ŠคํŒ…
        } catch (Exception e) {
            throw new RuntimeException("Unsafe instance cannot be obtained", e);
        }
    }
}

โœ… ์ด์ œ UnsafeUtil.getUnsafe()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์–ด๋””์„œ๋“  Unsafe ๊ฐ์ฒด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.


4๏ธโƒฃ CAS vs volatile vs ์ผ๋ฐ˜ ๋ณ€์ˆ˜ ๋น„๊ต ์‹คํ—˜

์ด๋ฒˆ ์‹คํ—˜์—์„œ๋Š” Product ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ ,

  • ์ผ๋ฐ˜ ๋ณ€์ˆ˜ (count)
  • volatile ๋ณ€์ˆ˜ (volatileCount)
  • CAS๋งŒ ์ ์šฉํ•œ ๋ณ€์ˆ˜ (countCAS())
  • CAS + volatile ์ ์šฉํ•œ ๋ณ€์ˆ˜ (volatileCountCAS())
    ์ด ๋„ค ๊ฐ€์ง€ ๋ฐฉ์‹์˜ ๋™์ž‘์„ ๋น„๊ตํ•œ๋‹ค.

5๏ธโƒฃ Product ํด๋ž˜์Šค: ์‹คํ—˜ ์ฝ”๋“œ

public class VolatileProduct {
    private int count = 0;
    private volatile int volatileCount = 0;

    private static final Unsafe unsafe = UnsafeUtil.getUnsafe();
    private final long countOffset;
    private final long volatileCountOffset;

    public VolatileProduct() throws NoSuchFieldException {
        this.countOffset = unsafe.objectFieldOffset(VolatileProduct.class.getDeclaredField("count"));
        this.volatileCountOffset = unsafe.objectFieldOffset(VolatileProduct.class.getDeclaredField("volatileCount"));
    }

    public void incrementCount() {
        count++;
    }

    public void incrementVolatileCount() {
        volatileCount++;
    }

    public void incrementCountCAS() {
        int oldValue;
        do {
            oldValue = unsafe.getInt(this, countOffset);
        } while (!unsafe.compareAndSwapInt(this, countOffset, oldValue, oldValue + 1));
    }

    public void incrementVolatileCountCAS() {
        int oldValue;
        do {
            oldValue = unsafe.getIntVolatile(this, volatileCountOffset);
        } while (!unsafe.compareAndSwapInt(this, volatileCountOffset, oldValue, oldValue + 1));
    }

    public int getCount() {
        return count;
    }

    public int getVolatileCount() {
        return volatileCount;
    }
}

6๏ธโƒฃ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ: CAS, volatile ๋น„๊ต

@Nested
    @DisplayName("incrementCount ๋ฉ”์„œ๋“œ๋Š”")
    class IncrementCountMethod {
        @Test
        @DisplayName("์ผ๋ฐ˜ ๋ณ€์ˆ˜๋ฅผ ์ฆ๊ฐ€ ์‹œํ‚ค๋Š”๋ฐ 10๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋กœ 1000๋ฒˆ ์‹คํ–‰ํ•ด๋„ 10000์ด ๋˜์ง€ ์•Š๋Š”๋‹ค")
        void should_not_ensure_consistency_with_plain_variable()
            throws InterruptedException, NoSuchFieldException {
            // Given
            VolatileProduct volatileProduct = new VolatileProduct();
            ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);

            // When
            for (int i = 0; i < THREAD_COUNT; i++) {
                VolatileProduct finalVolatileProduct = volatileProduct;
                executor.submit(() -> {
                    for (int j = 0; j < INCREMENT_PER_THREAD; j++) {
                        finalVolatileProduct.incrementCount();
                    }
                });
            }
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);

            // Then
            assertNotEquals(EXPECTED_COUNT, volatileProduct.getCount());
        }
    }

7๏ธโƒฃ ๊ฒฐ๊ณผ ๋ถ„์„

ํ…Œ์ŠคํŠธ ๋Œ€์ƒ๊ฒฐ๊ณผ๋™์‹œ์„ฑ ๋ณด์žฅ ์—ฌ๋ถ€์„ค๋ช…
incrementCount()โŒ ์‹คํŒจโŒ์ผ๋ฐ˜ ๋ณ€์ˆ˜๋Š” ๋™๊ธฐํ™” ๋ฌธ์ œ ๋ฐœ์ƒ
incrementVolatileCount()โŒ ์‹คํŒจโŒvolatile์€ ๊ฐ€์‹œ์„ฑ๋งŒ ๋ณด์žฅ, ์›์ž์„ฑ ๊นจ์ง
incrementCountCAS()โš ๏ธ ๋ถ€๋ถ„ ์„ฑ๊ณตโš ๏ธ ๋ถ€๋ถ„ ๋ณด์žฅCAS๋งŒ์œผ๋กœ ์›์ž์„ฑ์„ ์œ ์ง€ํ•˜์ง€๋งŒ ์ตœ์‹  ๊ฐ’ ๋ณด์žฅ X
incrementVolatileCountCAS()โœ… ์„ฑ๊ณตโœ…CAS + volatile๋กœ ๊ฐ€์‹œ์„ฑ๊ณผ ์›์ž์„ฑ ๋ชจ๋‘ ํ•ด๊ฒฐ

8๏ธโƒฃ ๊ฒฐ๋ก 

  • CAS๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์‹  ๊ฐ’์„ ์ฝ์–ด์˜ค์ง€ ๋ชปํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ.
  • CAS + volatile์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ฉด์„œ ์ตœ์‹  ๊ฐ’์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Œ.
  • ๋”ฐ๋ผ์„œ AtomicInteger ๋“ฑ CAS ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ volatile์„ ์‚ฌ์šฉํ•จ. ๐Ÿš€

ํ›„๊ธฐ

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ƒ๋‹นํžˆ ๋ถˆ์•ˆ์ • ํ•ฉ๋‹ˆ๋‹ค. ์บ์‹ฑ์˜ ์˜ํ–ฅ์ธ์ง€ CAS์—ฐ์‚ฐ์„ ๋™๋ฐ˜ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋Š” ์•ˆ์ •์ ์œผ๋กœ ํ†ต๊ณผํ•˜์ง€๋งŒ CAS์—ฐ์‚ฐ์ด ์—†๋Š” ํ…Œ์ŠคํŠธ๋Š” ๊ฐ๊ฐ ๋”ฐ๋กœ ์ง„ํ–‰ํ•˜๋ฉด ํ†ต๊ณผํ•˜์ง€๋งŒ ์ „์ฒด์ ์œผ๋กœ ๋Œ๋ฆฌ๋ฉด ๋งค๋ฒˆ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ ธ ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๊ฒ ์Šต๋‹ˆ๋‹ค. ๋” ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด