Synchronized

Synchronization is built around an internal entity known as the intrinsic lock  or monitor lock (The API specification often refers to this entity simply as a "monitor.")

https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

 

Intrinsic Locks and Synchronization (The Java™ Tutorials > Essential Java Classes > Concurrency)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

https://www.baeldung.com/cs/monitor

  • Synchronized를 사용하여 동기화를 하는 경우 Monitor 방식으로 한다고 합니다.
  • 그렇다면 모니터 방식은 대체 무엇일까요?

모니터

  • 스레드가 상호 배제와 협력을 가질 수 있도록 하는 동기화 메커니즘입니다.
  • 상호 배제란 하나의 스레드만 Lock을 사용하여 특정 시점에 메서드를 실행 시킬 수 있는걸 의미합니다.
  • 협력이란 wait-set을 사용하여 특정 조건이 충족될 때까지 스레드를 대기시킵니다.

Java에서의 Monitor 구현

자바에서는 모든 객체가 Monitor가 될 수 있습니다. 즉, 모든 객체가 고유 잠금을 가지고 있습니다.

  • JVM에서 모든 객체와 클래스는 논리적으로 모니터와 연결되며 모니터의 상호 배제 기능을 구현하기 위해 잠금(Mutex)이 각 개체 및 클래스에 연결됩니다.
  • java.lang.Object에 notify 메소드와 wait 메소드가 존재합니다.

Lock을 얻기 위해서 여러 작업이 요청하는 경우 JVM의 스케쥴러가 하나의 작업을 선택합니다.

  • 이 때, 우선순위에 따라서 결정하며, 우선순위가 같은 경우 FIFO 방식으로 가져옵니다.

스레드를 일시 중지하는 wait() 메서드와 객체를 기다리고 있는 다른 스레드를 깨우기 위한 notify()메서드를 사용하여 구현합니다.

  • wait() : 쓰레드가 일시 중단되고 대기 세트에 추가되어 다른 쓰레드가 동일한 객체에 대해 notify()나 notifyAll()을 호출할 때까지 대기합니다.
  • notify() : 대기 세트에서 임의의 쓰레드가 깨어나도록 통지합니다. 어떤 쓰레드를 깨울 것인지 선택하는건 비결정적이며 JVM에 따라 다릅니다.
  • notifyAll() : 대기 세트에서 기다리고 있는 모든 쓰레드를 깨웁니다.

 

Volatile

  • 공유 필드의 최신 값을 보도록 설정합니다. CPU 캐시 등의 이유로 공유 필드의 최신 값을 읽지 못하는 문제가 발생할 수도 있습니다.

https://nesoy.github.io/articles/2018-06/Java-volatile

https://www.baeldung.com/java-volatile

 

Guide to the Volatile Keyword in Java | Baeldung

Learn about the Java volatile keyword and its capabilities.

www.baeldung.com

 

Atomic 클래스

CAS(Compare-and-Swap)을 활용하여 데이터 무결성을 보장합니다. CAS는 세개의 피연산자에서 작동합니다. 하나는 작동할 메모리 위치, 하나는 변수의 기존 기대값, 마지막 하나는 설정해야 하는 새 값입니다.

 

즉, CAS는 메모리 위치에 있는 값을 새 값으로 업데이트하는 방법입니다. 단, 이전 값이 변수의 기존 기대값과 같아야 합니다.

 

이번 예시에서는 AtomicInteger의 accumulateAndGet() 메서드 구조를 통해 어떻게 동시성을 유지하는지 확인해 보겠습니다.

public final int accumulateAndGet(int x,
                                      IntBinaryOperator accumulatorFunction) {
    int prev = get(), next = 0;
    for (boolean haveNext = false;;) {
        if (!haveNext)
            next = accumulatorFunction.applyAsInt(prev, x);
        if (weakCompareAndSetVolatile(prev, next))
            return next;
        haveNext = (prev == (prev = get()))
  1. weakCompareAndSetVolatile(expected, newValue) 메서드는 현재 값이 매개변수로 전달된 expectedValue와 같은 경우 AtomicReference에 대한 값을 원자적으로 newValue로 설정하는데 사용됩니다. 즉, 현재 값이 prev인 경우에만 next를 리턴하게 됩니다.
  2. haveNext는 기존 값이 변경되었는지의 여부입니다. 
    1. (prev == (prev = get())) 에서 기존 값과 실제 저장되어 있는 데이터를 비교하며 실제 저장되어 있는 데이터로 prev를 갱신합니다.
    2. haveNext가 False인 경우가 실제 저장된 데이터와 현재 데이터가 다른 것이므로, accumulatorFunction.applyAsInt 메서드의 결과값을 새롭게 next에 넣어줍니다.
  3. 결국 최신 값으로 prev가 설정이 되고 나서야 weakCompareAndSetVolatile 메서드에 의해 next로 값이 설정되고 리턴됩니다.

 

https://www.baeldung.com/java-atomic-variables

 

'Language > Java' 카테고리의 다른 글

동시성 테스트와 CountdownLatch  (0) 2022.12.08
Collection 얕게 알아보기 (1)  (0) 2022.11.08
Final 키워드만 써도 성능 향상이 된다고?  (2) 2022.10.31
JVM의 동작원리  (0) 2022.10.22
lambda 예외 핸들링  (0) 2022.10.02

+ Recent posts