πŸ“š [Java] - volatile

CodeByHanΒ·2025λ…„ 11μ›” 8일

μžλ°”

λͺ©λ‘ 보기
11/13

μ–Όλ§ˆ μ „ λ©€ν‹°μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ λ°œμƒν•œ κ°€μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ volatile ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•œ 적이 μžˆλ‹€. κ·Έ κ³Όμ •μ—μ„œ 이 ν‚€μ›Œλ“œκ°€ μ •ν™•νžˆ μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€, 그리고 μ–Έμ œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ”μ§€ κΆκΈˆν•΄μ‘Œλ‹€. 이번 κΈ€μ—μ„œλŠ” volatile의 λ™μž‘ 원리와 νŠΉμ§•μ„ 정리해보겠닀.

CPU의 λ©”λͺ¨λ¦¬ ꡬ쑰

일단 λ©”λͺ¨λ¦¬ ꡬ쑰λ₯Ό μ‚΄νŽ΄λ³΄μž

CPU의 각 μ½”μ–΄μ—λŠ” μ„±λŠ₯ ν–₯상을 μœ„ν•΄ L1 μΊμ‹œκ°€ λ‚΄μž₯λ˜μ–΄ μžˆλ‹€.

CPUλŠ” λ©”λͺ¨λ¦¬μ—μ„œ μ½μ–΄μ˜¨ 값을 μΊμ‹œμ— μ €μž₯ν•˜κ³ , 이후 λ™μΌν•œ 데이터λ₯Ό λ‹€μ‹œ μ‚¬μš©ν•  λ•Œ μΊμ‹œμ—μ„œ 직접 읽어 더 λΉ λ₯΄κ²Œ μ²˜λ¦¬ν•œλ‹€.

데이터λ₯Ό 읽을 λ•ŒλŠ” λ¨Όμ € μΊμ‹œμ— ν•΄λ‹Ή 값이 μžˆλŠ”μ§€ ν™•μΈν•˜κ³ , 없을 경우 메인 λ©”λͺ¨λ¦¬μ—μ„œ λΆˆλŸ¬μ˜¨λ‹€.

이런 ꡬ쑰 λ•Œλ¬Έμ— μ—¬λŸ¬ μ½”μ–΄κ°€ λ™μ‹œμ— 같은 λ©”λͺ¨λ¦¬ μ£Όμ†Œλ₯Ό μ ‘κ·Όν•  λ•Œ, λ©”λͺ¨λ¦¬μ˜ 값이 λ³€κ²½λ˜μ—ˆμ§€λ§Œ μΊμ‹œκ°€ 아직 κ°±μ‹ λ˜μ§€ μ•Šμ•„ μ„œλ‘œ λ‹€λ₯Έ 값을 μ°Έμ‘°ν•˜λŠ” ν˜„μƒμ΄ λ°œμƒν•  수 μžˆλ‹€.

ν•œλ²ˆ μ½”λ“œλ‘œ ν…ŒμŠ€νŠΈλ₯Ό ν•΄λ³΄μž!!

public class VisibilityExample {

    boolean active = true;

    public void runTest() {
        new Thread(() -> {
            int counter = 0;
            while (active) {
                counter++;
            }
            System.out.println("μž‘μ—… μŠ€λ ˆλ“œ μ’…λ£Œ. μ΅œμ’… 카운트: " + counter);
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            System.out.println("μ œμ–΄ μŠ€λ ˆλ“œ: μž‘μ—… μŠ€λ ˆλ“œλ₯Ό μ€‘μ§€ν•©λ‹ˆλ‹€.");
            active = false;
        }).start();
    }

    public static void main(String[] args) {
        new VisibilityExample().runTest();
    }
}

첫 번째 μŠ€λ ˆλ“œλŠ” running ν”Œλž˜κ·Έλ₯Ό ν™•μΈν•˜λ©΄μ„œ count 값을 계속 μ¦κ°€μ‹œν‚¨λ‹€.

두 번째 μŠ€λ ˆλ“œλŠ” 1초 ν›„ running ν”Œλž˜κ·Έλ₯Ό false둜 λ³€κ²½ν•œλ‹€.

μš°λ¦¬λŠ” 두 번째 μŠ€λ ˆλ“œκ°€ μ‹€ν–‰λ˜λ©΄ 첫 번째 μŠ€λ ˆλ“œμ˜ λ¬΄ν•œ 루프가 μ’…λ£Œλ˜κ³  "μž‘μ—… μŠ€λ ˆλ“œ μ’…λ£Œ. μ΅œμ’… 카운트: " + counter κ°€ 좜λ ₯될 κ²ƒμœΌλ‘œ μ˜ˆμƒν•œλ‹€.

κ·ΈλŸ¬λ‚˜ μ‹€μ œλ‘œ 싀행해보면 첫 번째 μŠ€λ ˆλ“œλŠ” μ’…λ£Œλ˜μ§€ μ•ŠλŠ”λ‹€.

πŸ€” μ΄μœ κ°€ 뭘까?

μŠ€λ ˆλ“œ 1은 running λ³€μˆ˜λ₯Ό 읽을 λ•Œ μžμ‹ μ˜ CPU μΊμ‹œμ— μ €μž₯된 값을 μ°Έμ‘°ν•œλ‹€.

반면, μŠ€λ ˆλ“œ 2λŠ” μžμ‹ μ˜ CPU μΊμ‹œμ— μžˆλŠ” running 값을 false둜 λ³€κ²½ν•˜κΈ° λ•Œλ¬Έμ—, 두 μŠ€λ ˆλ“œλŠ” λ™μΌν•œ λ³€μˆ˜ 이름을 μ‚¬μš©ν•˜λ”λΌλ„ μ„œλ‘œ λ‹€λ₯Έ λ©”λͺ¨λ¦¬ μ˜μ—­μ˜ 값을 보고 μžˆλŠ” μ…ˆμ΄ λœλ‹€.

μ΄λŸ¬ν•œ 점을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ volatileλ₯Ό μ‚¬μš©ν•˜λŠ” 것이닀.

πŸ“Œ volatile

volatile λŠ” ν•΄λ‹Ή λ³€μˆ˜λ₯Ό CPU μΊμ‹œκ°€ μ•„λ‹Œ 메인 λ©”λͺ¨λ¦¬(Main Memory) λ₯Ό 톡해 읽고 쓰도둝 보μž₯ν•˜λŠ” ν‚€μ›Œλ“œμ΄λ‹€.

μœ„μ— μ½”λ“œμ˜ active에 volatileλ₯Ό λΆ™μ—¬λ³΄μž

package com.nexon;

public class VisibilityExample {

    volatile boolean active = true;

    public void runTest() {
        new Thread(() -> {
            int counter = 0;
            while (active) {
                counter++;
            }
            System.out.println("μž‘μ—… μŠ€λ ˆλ“œ μ’…λ£Œ. μ΅œμ’… 카운트: " + counter);
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            System.out.println("μ œμ–΄ μŠ€λ ˆλ“œ: μž‘μ—… μŠ€λ ˆλ“œλ₯Ό μ€‘μ§€ν•©λ‹ˆλ‹€.");
            active = false;
        }).start();
    }

    public static void main(String[] args) {
        new VisibilityExample().runTest();
    }
}

μ½”λ“œλ₯Ό μ‹€ν–‰μ‹œν‚€λ©΄ 1μ΄ˆν›„μ— ν”„λ‘œκ·Έλž¨μ΄ μ’…λ£Œλ˜λŠ” 것을 확인 ν•  수 μžˆλ‹€.

이처럼 volatile ν‚€μ›Œλ“œλŠ” λ‹¨μˆœνžˆ λ³€μˆ˜μ˜ 값을 메인 λ©”λͺ¨λ¦¬μ—μ„œ 읽고 쓰도둝 보μž₯ν•΄ κ°€μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•œλ‹€.

ν•˜μ§€λ§Œ μ›μžμ„±(atomicity) 은 보μž₯ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ—, 볡합적인 μ—°μ‚°(예: count++)μ—μ„œλŠ” μ—¬μ „νžˆ 동기화(synchronized)κ°€ ν•„μš”ν•˜λ‹€.

즉, volatile은 β€˜μŠ€λ ˆλ“œ κ°„ 값이 μ¦‰μ‹œ λ°˜μ˜λ˜μ–΄μ•Ό ν•˜λŠ” κ²½μš°β€™μ—λ§Œ μ‚¬μš©ν•΄μ•Ό ν•˜λ©°, λ™μ‹œμ— μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ 값을 μˆ˜μ •ν•˜λŠ” 상황이라면 락(lock) 을 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.


🧹 정리

volatileκ°€ λ­”κ°€μš”? μ–Έμ œ μ‚¬μš©ν•΄μš”?

volatile ν‚€μ›Œλ“œλŠ” λ™μ‹œμ„± ν”„λ‘œκ·Έλž˜λ°μ—μ„œ λ°œμƒν•  수 μžˆλŠ” κ°€μ‹œμ„± 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€. κ°€μ‹œμ„± λ¬Έμ œλž€, μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ λ™μ‹œμ— λ³€μˆ˜λ₯Ό μ‚¬μš©ν•  λ•Œ, 각 μŠ€λ ˆλ“œμ˜ CPU μΊμ‹œμ™€ 메인 λ©”λͺ¨λ¦¬(RAM) κ°„ 값이 μΌμΉ˜ν•˜μ§€ μ•Šμ•„ ν•œ μŠ€λ ˆλ“œκ°€ λ³€κ²½ν•œ 값을 λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ λ°”λ‘œ ν™•μΈν•˜μ§€ λͺ»ν•˜λŠ” 상황을 μ˜λ―Έν•œλ‹€. volatile을 뢙인 λ³€μˆ˜λŠ” λͺ¨λ“  μŠ€λ ˆλ“œκ°€ 메인 λ©”λͺ¨λ¦¬μ—μ„œ 직접 읽고 쓰도둝 보μž₯λ˜λ―€λ‘œ, κ°’μ˜ 변경이 μ¦‰μ‹œ λ‹€λ₯Έ μŠ€λ ˆλ“œμ— λ°˜μ˜λœλ‹€.

이 ν‚€μ›Œλ“œλŠ” μŠ€λ ˆλ“œ κ°„ μƒνƒœλ₯Ό λ‹¨μˆœνžˆ κ³΅μœ ν•˜λŠ” ν”Œλž˜κ·Έ λ³€μˆ˜λ‚˜, 읽기/μ“°κΈ° μ—°μ‚°λ§Œ ν•„μš”ν•œ κ²½μš°μ— μ‚¬μš©λœλ‹€. ν•˜μ§€λ§Œ volatile은 μ›μžμ„±(atomicity)을 보μž₯ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ—, μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ λ™μ‹œμ— 값을 μˆ˜μ •ν•˜λŠ” 볡합 μ—°μ‚°μ—μ„œλŠ” synchronizedλ‚˜ Atomic 클래슀λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

μ°Έκ³ 

[JAVA] Volatile μ΄λž€?

profile
λ…Έλ ₯은 λ°°μ‹ ν•˜μ§€ μ•Šμ•„ πŸ”₯

0개의 λŒ“κΈ€