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
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
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()))
- weakCompareAndSetVolatile(expected, newValue) 메서드는 현재 값이 매개변수로 전달된 expectedValue와 같은 경우 AtomicReference에 대한 값을 원자적으로 newValue로 설정하는데 사용됩니다. 즉, 현재 값이 prev인 경우에만 next를 리턴하게 됩니다.
- haveNext는 기존 값이 변경되었는지의 여부입니다.
- (prev == (prev = get())) 에서 기존 값과 실제 저장되어 있는 데이터를 비교하며 실제 저장되어 있는 데이터로 prev를 갱신합니다.
- haveNext가 False인 경우가 실제 저장된 데이터와 현재 데이터가 다른 것이므로, accumulatorFunction.applyAsInt 메서드의 결과값을 새롭게 next에 넣어줍니다.
- 결국 최신 값으로 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 |