Skip to content

Instantly share code, notes, and snippets.

@devwithpug
Last active January 11, 2022 09:02
Show Gist options
  • Save devwithpug/2314112733183008cfaa6c44a23d7cad to your computer and use it in GitHub Desktop.
Save devwithpug/2314112733183008cfaa6c44a23d7cad to your computer and use it in GitHub Desktop.
자바에서 동기화를 해결하는 3가지 방법

자바에서 동기화를 해결하는 3가지 방법

1. synchronized

메서드 앞에 키워드를 붙여서 선언

public synchronized void foo() {}

특정 Thread가 블록 전체를 lock 하므로, 간단하면서 완벽한 thread-safe -> 다른 Thread는 아무런 작업을 하지 못하고 기다릴 수 밖에 없어 자원의 낭비 발생 가능

2. volatile

변수 앞에 키워드를 붙여서 선언

public volatile String str = "VOLATILE STRING";

Thread는 실행되고 있는 CPU 메모리 영역에 데이터를 캐싱함. 따라서 멀티 코어 프로세서에서 다수의 Thread가 변수 a를 공유하더라도 캐싱된 시점에 따라 데이터가 다를 수 있음(멀티 코어) -> 이말은 즉, 캐싱된 데이터가 언제 갱신되는지 또한 정확히 알 수 없다는 것

이런 경우 volatile 키워드를 사용하여 CPU 메모리 영역에 캐싱된 값이 아니라 항상 최신의 값을 가지도록 메인 메모리 영역에서 값을 참조하도록 할 수 있음 -> 즉, 동일 시점에 모든 스레드가 동일한 값을 가지도록 동기화함.

하지만 volatile을 통해 모든 동기화 문제가 해결되는 건 아님. (단지 모든 Thread가 캐시없이 최신의 값을 보게 할 뿐)

  1. 변수 a =1
  2. 1번 스레드가 a에 값을 증가시키기 위해 a의 값을 읽습니다. a=1
  3. 1번 스레드가 1 + 1을 계산하여 2를 얻습니다. (아직 저장은 안 함)
  4. 2번 스레드가 a에 값을 증가시키기 위해 a의 값을 읽습니다. a=1
  5. 1번 스레드가 변수 a에 2를 저장
  6. 2번 스레드가 1 + 1을 계산하여 2를 얻습니다. (아직 저장은 안 함)
  7. 2번 스레드가가 변수 a에 2를 저장

-> 최종 값이 2가 되는 문제

즉, volatile은 원자적 연산에서만 동기화를 보장한다.

3. Atomic 클래스

위 문제들을 해결하기 위해, 비-원자적 연산에서도 동기화를 빠르고 쉽게 이용하기 위한 클래스 모음

java.util.concurrent.* (대표적으로 컬렉션, Wrapper 클래스 등이 있음)

Non-Blocking 이 가능한 이유는 CAS(Compare-And-Swap) 알고리즘을 이용하기 때문

volatile 키워드를 이용하면서 현재 Thread에 저장된 값과 메인 메모리에 저장된 값을 비교함

  • 일치하는 경우 새로운 값으로 교체(thread-safe한 상태이므로 로직 수행)
  • 일치하지 않는 경우 실패 후 재시도(thread-safe하지 않은 상태였으므로 재시도)
// java.util.concurrent.atomic.AtomicLong;
public class AtomicLong extends Number implements java.io.Serializable {
	
    private volatile long value;
	
    public final long incrementAndGet() {
        return U.getAndAddLong(this, VALUE, 1L) + 1L;
    }
	
}

// 자바 네이티브 코드 부분 (jdk.internal.misc.Unsafe)
// weakCompareAndSetLong() : CAS 알고리즘
// AtomicLong.incrementAndGet() 메서드를 쭉 따라가면 JNI 코드로 이루어져 있음
// 메모리에 저장된 값과 CPU에 캐시된 값을 비교해 동일한 경우에만 update 수행
    public final long getAndAddLong(Object o, long offset, long delta) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!weakCompareAndSetLong(o, offset, v, v + delta));
        return v;
    }

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment