Post

Synchronize, Volatile, Atomic Type과 CAS

Synchronize, Volatile, Atomic Type과 CAS

<목표>

atomic Type과 CAS는 무엇이고 언제 사용되는 것인가?

  • 왜 사용하는지?
  • ReentrantLock 을 사용하는 방법과 차이점

멀티 스레드에서 동시성 문제

  • 멀티 스레드 환경에서 스레드들이 공유 자원에 대해 접근하여 수정하려 한다면 다른 스레드가 저장하기 이전에 로드 작업이 일어날 가능성이 있다.
  • 이를 동시성 문제라고 하는데 자바에서는 이를 해결하는 방법으로 synchronized, volatile, Atomic 등이 있다.

Synchronized

  • 한 번에 하나의 스레드만 공유 리소스에 액세스할 수 있도록 허용하고 다른 모든 스레드는 해당 액세스 스레드가 공유 리소스에 대한 액세스를 해제할 때까지 기다리도록 하는 것이다.

  • 자바에서 스레드는 객체 상태 정보를 스스로 들고다니며 스레드가 변경한 내용은 메인 메모리로 곧장 반영되고 같은 데이터를 액세스하는 다른 스레드가 다시 읽는 구조이다.
  • 자바 synchronized 키워드는 모니터를 장악한 스레드의 로컬 뷰가 메인 메모리와 동기화되었다는 뜻이다.
  • 따라서 동기화 메소드, 동기화 블록은 스레드가 반드시 동기를 맞춰야 할 접점에 해당하며 다른 동기화 메소드/블록이 시작되기 전에 반드시 완료되어야 할 코드 블록을 정의해 놓은 것이다.
  • 그러나 기존 자바 synchronized 락은 여러 한계점이 있다.
    • 락이 걸린 객체에서 일어나는 동기화 작업은 모두 균등하게 취급된다.
    • 락 획득/해제는 반드시 메소드 수준이나 메소드 내부의 동기화 블록 안에서 이루어져야한다.
    • 락을 얻지 못한 스레드는 블로킹된다. 락을 얻지 못할 경우 락을 얻어 처리를 계속하려고 시도하는 것 조차 불가능하다.

volatile

  • 변수를 CPU 캐시가 아닌 main memory에 저장하겠다는 것을 명시하는 것인데 매번 변수의 값을 read할 때마다 CPU cache에 저장된 값이 아닌 main memory에서 읽는 것이다. 또한 변수의 값을 write할 때도 main memory에 쓰는 것이다.
  • volatile은 컴파일러에게 이 데이터(변수, 메소드)에 대해 멀티 스레드로 접근하고 있음을 알려주는 것인데 읽기, 쓰기에 대해서 동기화를 보장한다.
  • synchronized는 행위에 대한 동기화이며 volatile은 행위의 타겟에 대한 동기화이다.
  • 하나의 변수를 멀티 스레드에서 사용하게 되면 각 스레드마다 해당 변수 값을 저장하는 작업 복사본(cpu cache)을 하나씩 가지고 있어서 스레드는 원본 값을 접근하기 위해 자신의 작업 복사본에 값을 저장하고 어떠한 연산을 거친 뒤 다시 원본 값을 쓰는 방식을 취하는데 이러한 경우 동기화에 문제가 있다. 만약 화면에 변수 값을 찍는 작업을 위해 하나의 스레드가 자신의 작업 복사본에 값을 읽어 들이고 그 값을 찍기 이전에 다른 스레드가 그것을 변경했다고 한다면 한 쪽 스레드는 변경되기 이전의 값이 존재하고 다른 한 쪽은 변경된 값을 가지게 된다.
  • 이것을 막기위해 volatile을 사용하면 스레드가 특정 변수를 접근하려는 연산이 발생하면 무조건 다시 원본의 값을 가져오게 하는 것이다.
  • volatile은 하나의 thread가 write하고 나머지 threaad가 읽는 상황인 경우 변수의 값이 최신의 값으로 읽어와야 하는 경우 등에 사용하면 되지만 성능에 영향을 주는 부분은 고려해야한다.

Atomic

  • volatile은 읽기 쓰기에 대한 동기화를 보장하고 연산에 대해서는 동기화를 보장하지 않으므로 변수가 thread에 안전하려면 java.util.concurrent.atomic.Atomic* 클래스를 사용한다.
  • Atomic 변수는 volatile의 확장판이라고도 할 수 있지만 volatile보다 더 유연해서 상태 의존적 업데이트를 안전하게 수행할 수 있다.
  • Atomics는 자신이 감싸고 있는 베이스 타입을 상속하지 않고 직접 대체하는 것도 허용되지않는다.
  • synchronized와는 다르게 blocking이 아닌 non-blocking하면서 원자성을 보장하여 동기화 문제를 해결한다.
  • atomic의 핵심 동작 원리는 CAS(Compare And Swap) 알고리즘이다

CAS 알고리즘

  • CAS 알고리즘의 동작 원리는 다음과 같다.
  • 인자로 기존 값(Compared Value)과 변경할 값(Exchanged Value)을 전달한다.
  • 기존 값(Compared Value)이 현재 메모리가 가지고 있는 값(Destination)과 같다면 변경할 값(Exchanged Value)을 반영하며 true를 반환한다.
  • 반대로 기존 값(Compared Value)이 현재 메모리가 가지고 있는 값(Destination)과 다르다면 값을 반영하지 않고 false를 반환한다.

<정리>

1. atomic Type과 CAS는 무엇이고 언제, 왜 사용되는 것인가?

  • atomic type은 멀티 스레드 환경에서 원자성을 보장하기 위한 개념이다. CAS 알고리즘을 통해 non-blocking하면서 가시성과 원자성을 보장해 동기화 문제를 해결한다.
  • CAS 알고리즘은 현재 스레드가 가지고 있는 기존값과 메모리가 가지고 있는 값을 비교해 같은 경우 변경할 값을 메모리에 반영하고 true를 반환한다. 다른 경우에는 변경값이 반영되지 않고 false를 반환한 다음 재시도를 하는 방식으로 동작한다. CAS 알고리즘을 통해 가시성과 원자성 문제를 해결할 수 있다.

3. ReentrantLock 을 사용하는 방법과 차이점

  • ReentrantLock 이란 동기화된 메소드와 문장을 사용하여 액세스 할 수 있는 암시적인 모니터 잠금 기능과 같은 기본적인 동작과 의미를 가진 Reentrant 상호 간의 상호 배제된 상호 배제 잠금 기능을 의미한다.
  • atomic type은 synchronized와는 다르게 blocking이 아닌 non-blocking하면서 원자성을 보장하여 동기화 문제를 해결한다.
This post is licensed under CC BY 4.0 by the author.