Java & Kotlin

[Java] 동기화 - synchronized와 volatile, Atomic Class에 λŒ€ν•˜μ—¬.

μžνλ‹ˆ 2022. 3. 6. 02:22

0.πŸšΆλ“€μ–΄κ°€λ©°

μžλ°” μŠ€λ ˆλ“œμ— λŒ€ν•΄ κ³΅λΆ€ν•˜λ‹€ 동기화 κ΄€λ ¨ κ°œλ…λ“€μ„ μ•Œκ²Œ λ˜μ—ˆκ³  이 뢀뢄을 처음 μ ‘ν–ˆμ„ λ•Œ ν˜Όλž€μŠ€λŸ¬μšΈ μ •λ„λ‘œ ν—·κ°ˆλ ΈμŠ΅λ‹ˆλ‹€,, 각 ν‚€μ›Œλ“œμ— λŒ€ν•΄ μ•Œ 것 같닀가도 λ‹€μ‹œ 보면 ν—·κ°ˆλ¦¬λŠ” 그런 상황이 λ°˜λ³΅λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 

λ§Žμ€ λ‚΄μš©μ˜ 글을 보닀가 effective java에 λ‚˜μ˜¨ λ™μ‹œμ„± κ΄€λ ¨ 챕터가 κ°€μž₯ 도움이 됐던 것 κ°™μ•„ 이λ₯Ό λΆ€λΆ„μ μœΌλ‘œ μΈμš©ν•˜λ©° κ΄€λ ¨ κ°œλ…λ“€μ— λŒ€ν•΄ 글을 써보렀 ν•©λ‹ˆλ‹€. 각 κ°œλ…μ˜ μžμ„Έν•œ λ‚΄μš©μ΄λ‚˜ μ‚¬μš©λ²•μ— λŒ€ν•΄μ„œλŠ” μžλ£Œλ“€μ΄ λ§Žμ•„ 제 κΈ€μ—μ„œλŠ” 닀루지 μ•Šμ„ 것이고 μ œκ°€ ν—·κ°ˆλ Έλ˜ μ„Έ κ°œλ… κ°„μ˜ 차이λ₯Ό ν™œμš© μ˜ˆμ‹œλ₯Ό μ‚¬μš©ν•΄ μ†Œκ°œν•΄λ³ΌκΉŒ ν•©λ‹ˆλ‹€.  


1.πŸ‘«synchronized

synchronized ν‚€μ›Œλ“œλŠ” λ©”μ„œλ“œλ‚˜ 블둝을 ν•œ λ²ˆμ— ν•œ μŠ€λ ˆλ“œλ§Œ μˆ˜ν–‰ν•˜λ„λ‘ 보μž₯ν•΄μ€λ‹ˆλ‹€. λ™κΈ°ν™”μ˜ λŒ€ν‘œμ μΈ κΈ°λŠ₯인 배타적 싀행이죠. ν•˜μ§€λ§Œ λ™κΈ°ν™”μ—λŠ” 사싀 두 가지 κΈ°λŠ₯이 μžˆμŠ΅λ‹ˆλ‹€. 

  • 배타적 μ‹€ν–‰(μ›μžμ„±)을 보μž₯ν•œλ‹€.
  • ν•œ μŠ€λ ˆλ“œκ°€ λ§Œλ“  λ³€ν™”λ₯Ό λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ 확인할 수 있게 ν•΄μ€€λ‹€.(μŠ€λ ˆλ“œ κ°„ 톡신, κ°€μ‹œμ„±)

두 번째 κΈ°λŠ₯이 λ‚―μ„€μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. Visibility라고도 λΆˆλ¦¬λŠ” κ°œλ…μΈλ° μ΄λŠ” μŠ€λ ˆλ“œ μ‚¬μ΄μ˜ μ•ˆμ •μ μΈ 톡신을 μœ„ν•΄ κΌ­ ν•„μš”ν•©λ‹ˆλ‹€.

 

μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄λ„λ‘ ν•˜μ£ .

import java.util.concurrent.TimeUnit;

public class Test {
    private static boolean stop;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            int i = 0;
            while(!stop)
                i++;
        });
        t.start();

        TimeUnit.SECONDS.sleep(1);
        stop = true;
    }
}

μœ„ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄λ©΄ 1μ΄ˆκ°€ μ§€λ‚œ λ’€ stop = true둜 λ°”λ€Œμ–΄ ν”„λ‘œκ·Έλž¨μ΄ μ’…λ£Œλ  수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ μ‹€ν–‰μ‹œμΌœλ³΄λ©΄ μ˜ˆμƒλŒ€λ‘œ μž‘λ™ν•˜μ§€ μ•Šμ£ .. 원인은 λ°”λ‘œ 동기화 λ•Œλ¬Έμž…λ‹ˆλ‹€.

 

stop을 λ°”κΎΈλŠ” 건 main μŠ€λ ˆλ“œμΈλ° μ–Έμ œμ―€μ—μ„œμ•Ό t μŠ€λ ˆλ“œκ°€ 바뀐 stop을 visible 할지 λͺ¨λ¦…λ‹ˆλ‹€. 이외에도 JVM의 μ½”λ“œ μ΅œμ ν™”λ‘œ 인해 생각과 λ‹€λ₯΄κ²Œ μ½”λ“œκ°€ 변경될 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ μ•„λž˜μ™€ 같은 μ½”λ“œλ‘œ 동기화 처리λ₯Ό ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

import java.util.concurrent.TimeUnit;

public class Test {
    private static boolean stop;

    private static synchronized void requestStop(){
        stop = true;
    }

    private static synchronized boolean isStop(){
        return stop;
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            int i = 0;
            while(!isStop())
                i++;
        });
        t.start();

        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

바뀐 뢀뢄은 6 ~ 12 라인과 while의 쑰건문, 23 λΌμΈμž…λ‹ˆλ‹€. stop ν•„λ“œλ₯Ό λ‹€λ£¨λŠ” λ©”μ„œλ“œλ₯Ό λ™κΈ°ν™”ν•˜λ©΄ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό 얻을 수 있게 되죠.

 

requestStopλ©”μ„œλ“œμ™€ isStopλ©”μ„œλ“œλŠ” 사싀 동기화 없이도 μ›μžμ μœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€. 

 

λ”°λΌμ„œ μœ„ μ˜ˆμ‹œλŠ” λ™κΈ°ν™”μ˜ κΈ°λŠ₯ 두 번째인 μŠ€λ ˆλ“œ κ°„ 톡신을 μœ„ν•΄ 동기화 처리λ₯Ό ν•œ μ˜ˆμ‹œλΌκ³  λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.


2.πŸ—»volatile

synchronized ν‚€μ›Œλ“œλŠ” μƒλ‹Ήνžˆ λΉ„μš©μ΄ λ“œλŠ” μ½”λ“œμž…λ‹ˆλ‹€. λͺ¨λ“  μ½”μ–΄κ°€ ν•΄λ‹Ή λ©”λͺ¨λ¦¬λ₯Ό μΌκ΄€λ˜κ²Œ λ°”λΌλ³΄λŠ” μ§€μ—°μ‹œκ°„μ΄ λ©€ν‹° μ½”μ–΄ ν™˜κ²½μ—μ„œ ꡉμž₯히 λ‚­λΉ„μž…λ‹ˆλ‹€. λ˜ν•œ synchronized ν‚€μ›Œλ“œλŠ” 가상 λ¨Έμ‹ μ˜ μ΅œμ ν™”λ„ λ°©ν•΄ν•˜μ£ .

 

λ”°λΌμ„œ 동기화 λͺ©μ  쀑 μŠ€λ ˆλ“œ κ°„μ˜ ν†΅μ‹ λ§Œμ΄ λͺ©μ μ΄λΌλ©΄ volatile ν‚€μ›Œλ“œλ₯Ό κ³ λ €ν•΄λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. volatile ν‚€μ›Œλ“œλ₯Ό λ³€μˆ˜ μ•žμ— λΆ™μ΄κ²Œ 되면 μ½”μ–΄λ§ˆλ‹€ μžˆλŠ” μΊμ‹œκ°€ μ•„λ‹Œ λ©”λͺ¨λ¦¬μ—μ„œ 직접 읽고 μ”λ‹ˆλ‹€. λ”°λΌμ„œ volatile ν‚€μ›Œλ“œλŠ” 항상 κ°€μž₯ μ΅œκ·Όμ— 기둝된 값을 읽게 됨을 보μž₯ν•©λ‹ˆλ‹€. 배타적 μˆ˜ν–‰κ³ΌλŠ” 관련이 μ—†μ§€λ§Œ 말이죠.(longμ΄λ‚˜ double의 경우 volatile ν‚€μ›Œλ“œκ°€ μ›μžμ„±μ„ 보μž₯ν•˜κΈ°λ„ 함.) λ˜ν•œ volatile ν‚€μ›Œλ“œλŠ” lock-free ν•˜κΈ° λ•Œλ¬Έμ— μ„±λŠ₯λ©΄μ—μ„œλ„ λ”μš± μ’‹μŠ΅λ‹ˆλ‹€.

 

μœ„μ—μ„œ μ†Œκ°œν•œ μ½”λ“œλŠ” 배타적 μˆ˜ν–‰κ³ΌλŠ” λ”±νžˆ 관련이 μ—†μœΌλ‹ˆ volatile ν‚€μ›Œλ“œλ‘œ λ°”κΏ”λ³΄κ² μŠ΅λ‹ˆλ‹€.

import java.util.concurrent.TimeUnit;

public class Test {
    private static volatile boolean stop;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            int i = 0;
            while(!stop)
                i++;
        });
        t.start();

        TimeUnit.SECONDS.sleep(1);
        stop = true;
    }
}

μœ„μ™€ 같이 μˆ˜μ •ν•΄μ£Όλ©΄ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ volatile ν‚€μ›Œλ“œλŠ” 배타적인 μˆ˜ν–‰, μ›μžμ μΈ μˆ˜ν–‰κ³ΌλŠ” 거리가 μ’€ μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ lock-free ν•˜λ©΄μ„œ μ›μžμ μΈ μˆ˜ν–‰μ„ ν•˜κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”?


3.🍎Atomic Class

 

 

java.util.concurrent.atomic (Java Platform SE 7 )

Class Summary  Class Description AtomicBoolean A boolean value that may be updated atomically. AtomicInteger An int value that may be updated atomically. AtomicIntegerArray An int array in which elements may be updated atomically. AtomicIntegerFieldUpdate

docs.oracle.com

java.util.concurrent.atomic νŒ¨ν‚€μ§€μ—λŠ” lock-free ν•˜λ©΄μ„œλ„ thread-safe ν•œ κΈ°λŠ₯을 μ§€μ›ν•˜λŠ” ν΄λž˜μŠ€λ“€μ΄ μžˆμŠ΅λ‹ˆλ‹€. 이름뢀터 μ›μžμ„±μ„ 보μž₯ν•  κ²ƒλ§Œ κ°™λ„€μš”. AtomicLong, AtomicInterger Class 등이 atomic package에 μžˆλŠ” λŒ€ν‘œμ μΈ ν΄λž˜μŠ€μž…λ‹ˆλ‹€.

 

Atomic Class듀은 μ–΄λ–»κ²Œ lock-free ν•˜λ©΄μ„œλ„ μ›μžμ„±μ„ 보μž₯ν• κΉŒμš”? κ΄€λ ¨ κ°œλ…μΈ CAS μ•Œκ³ λ¦¬μ¦˜μ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

# CAS(Compare And Swap) μ•Œκ³ λ¦¬μ¦˜

CAS μ•Œκ³ λ¦¬μ¦˜

CAS μ•Œκ³ λ¦¬μ¦˜μ˜ λ™μž‘ μ›λ¦¬λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • 인자둜 κΈ°μ‘΄ κ°’κ³Ό λ³€κ²½ν•  값을 μ „λ‹¬ν•œλ‹€.
  • κΈ°μ‘΄ 값이 ν˜„μž¬ λ©”λͺ¨λ¦¬κ°€ 가지고 μžˆλŠ” κ°’κ³Ό κ°™λ‹€λ©΄ λ³€κ²½ν•  값을 λ°˜μ˜ν•˜λ©° trueλ₯Ό λ¦¬ν„΄ν•œλ‹€.
  • κΈ°μ‘΄ 값이 ν˜„μž¬ λ©”λͺ¨λ¦¬κ°€ 가지고 μžˆλŠ” κ°’κ³Ό λ‹€λ₯΄λ‹€λ©΄ 값을 λ°˜μ˜ν•˜μ§€ μ•Šκ³  falseλ₯Ό λ¦¬ν„΄ν•œλ‹€.

μœ„ λ™μž‘ 원리λ₯Ό 보면 λ“œλŠ” 의문이 μžˆμ„ κ²ƒμž…λ‹ˆλ‹€. κΈ°μ‘΄ κ°’κ³Ό ν˜„μž¬ λ©”λͺ¨λ¦¬κ°€ 가지고 μžˆλŠ” 값이 λ‹€λ₯Έ 상황이 μžˆλ‚˜?라고 생각할 수 μžˆμŠ΅λ‹ˆλ‹€. Atomic은 배타적인 싀행을 lock을 κ±°λŠ” 방식을 μ‚¬μš©ν•˜μ—¬ 보μž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ μŠ€λ ˆλ“œ Aκ°€ 곡유 λ³€μˆ˜μ— λŒ€ν•΄ 계산을 ν•˜κ³  λ©”λͺ¨λ¦¬μ— λ°˜μ˜ν•˜λ €λŠ”λ° κ·Έ 사이에 λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ 곡유 λ³€μˆ˜λ₯Ό λ³€κ²½ν•œ κ²½μš°κ°€ μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ•Œ μŠ€λ ˆλ“œ AλŠ” μžμ‹ μ˜ 계산 값을 λ©”λͺ¨λ¦¬μ— λ°˜μ˜ν•˜λ©΄ μ•ˆ λ©λ‹ˆλ‹€. μ΅œμ‹  값이 μ•„λ‹Œ κ±Έ 가지고 κ³„μ‚°ν•œ μ…ˆμ΄ 되기 λ•Œλ¬Έμ΄μ£ . λ”°λΌμ„œ falseλ₯Ό λ°˜ν™˜ν•œ 경우 λ¬΄ν•œ 루프λ₯Ό 톡해 계산을 λ‹€μ‹œ μ‹œμž‘ν•©λ‹ˆλ‹€.  

 

μœ„μ™€ 같은 λ°©μ‹μœΌλ‘œ Atomic Type은 λ™κΈ°ν™”μ˜ 두 가지 κΈ°λŠ₯을 λͺ¨λ‘ μ œκ³΅ν•΄μ€λ‹ˆλ‹€.


4.πŸ’¨λ‚˜κ°€λ©°

μžλ°”μ˜ 동기화 μ²˜λ¦¬μ— λŒ€ν•΄ κ³΅λΆ€ν•˜λ©΄μ„œ κ°€μž₯ 도움이 됐던 λ¬Έμž₯은 "λ™κΈ°ν™”λŠ” 두 가지 κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€."μ˜€μŠ΅λ‹ˆλ‹€. 이것이 μ •λ¦¬λ˜μ–΄μžˆμ§€ μ•Šμ€ μƒνƒœμ—μ„œ μ—¬λŸ¬ 글듀을 μ‚΄νŽ΄λ³΄λ‹ˆ 각 λ°©μ‹μ˜ μž₯단점은 λ¬Όλ‘ , λ°©μ‹λ§ˆλ‹€ μ–΄λ–€ 차이가 있고 μ–΄λ–»κ²Œ ν™œμš©ν•΄μ•Ό ν•  지도 감이 잘 μ•ˆ μ™”μŠ΅λ‹ˆλ‹€.

 

ν•΄λ‹Ή λ¬Έμž₯을 잘 μ‚΄νŽ΄λ³΄μ‹œκ³  동기화 처리 방식에 λŒ€ν•΄ μ’€ 더 μ•Œμ•„λ³΄μ‹ λ‹€λ©΄ 도움이 λ˜μ‹€ 것 κ°™μŠ΅λ‹ˆλ‹€.

 

λ˜ν•œ μžλ°”μ˜ λ™μ‹œμ„± μ²˜λ¦¬λŠ” 깊이 λ“€μ–΄κ°€λ©΄ 끝없이 곡뢀해야 ν•  것 κ°™λ‹€λŠ” 생각을 ν–ˆμŠ΅λ‹ˆλ‹€. λ™μ‹œμ„± μ²˜λ¦¬ν•˜λŠ” 방식도 λ‹€μ–‘ν•˜λ©° μ£Όμ˜ν•΄μ•Ό ν•  점, λ‹€μ–‘ν•œ λ™μ‹œμ„± μœ ν‹Έλ¦¬ν‹° λ“± λ°©λŒ€ν•œ λ‚΄μš©μ΄ μžˆμ–΄ μΆ”ν›„ ν•„μš”ν•˜λ‹€κ³  생각이 λ“€ λ•Œ 깊이 곡뢀해보고 λ‹€μ‹œ 정리해보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€. 

λ°˜μ‘ν˜•