Java에서 마주할 수 있는 동시성에 대해서 알아보자.
| 동시성 vs 병렬성
동시성과 병렬성은 자주 비교된다.
- 동시성(Concurrency) : 하나의 CPU가 여러 작업을 빠르게 번갈아 수행함으로써, 사람이 보기에는 여러 작업이 동시에 진행되는 것처럼 보이는 상태이다.
- 1개의 CPU에서 작업을 나눠 실행하고, 스케줄러가 각 작업의 실행시간을 조율한다.
- 한 번에 한 작업만 실행되지만, 작업 간의 전환이 매우 빠르게 이루어진다.
- 병렬성(Parallelism) : 여러 CPU가 각각 독립적으로 여러 작업을 동시에 처리하는 상태이다.
- CPU N개가 N개의 작업을 동시에 처리한다.
- 멀티 코어 프로세서에서 여러 스레드가 동시에 실행된다.
지난번에, ArrayList와 Vector를 비교할 때, Thread-Safe에 대해 언급을 한 적이 있다.
Thread Safe에 대해 좀 더 알아보자.
| Thread Safe
Thread Safe는 멀티 스레드 환경에서 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램 실행에 문제가 없음을 뜻한다.
- 데이터 손실, 충돌, 또는 비정상적인 동작 없이 안정적으로 작동한다.
| 가시성 vs 원자성
가시성 문제와 원자성 문제는 여러 개의 스레드가 사용될 때 언급될 수 있는 문제들이다.
- 가시성 문제는 여러 개의 스레드가 사용됨에 따라, CPU 캐시 메모리와 RAM의 데이터가 서로 일치하지 않아 생기는 문제이다.
- CPU 캐시와 메인 메모리 간 데이터 불일치로 발생한다.
- 스레드 A가 값을 변경했지만, 스레드 B는 캐시된 값을 읽기 때문에 최신 값을 보지 못한다.
- 원자성 문제는 여러 개의 스레드가 공유 자원에 동시에 쓰기 연산을 할 경우 잘못된 결과를 반환하는 것을 의미한다.
- count++ 연산과 같이 두 스레드가 동시에 실행했을 때 값이 덮어써지는 예시를 많이 들기도 한다.
그렇다면, 동시성 문제는 어떻게 해결할 수 있을까?
- volatile 키워드 사용 (가시성 문제)
- synchronized (원자성 문제)
- atomic (원자성 문제)
| volatile
volatile은 CPU 캐시 메모리를 거치지 않고, RAM으로 직접 데이터를 읽고 쓰는 작업을 수행함으로써 가시성 문제를 해결할 수 있는 키워드이다.
- CPU 캐시를 우회하고, 항상 메인 메모리에서 값을 읽고 쓰도록 강제하는 것이다.
- 따라서, 스레드가 항상 최신 값을 읽고 쓸 수 있도록 보장한다.
- 하지만, 원자성을 보장하지 않아, 복잡한 연산(ex. count++)에는 적합하지 않다.
| synchronized
synchronized는 lock을 통해 동기화를 수행하여 원자성 문제를 해결하는 키워드 이다. 메소드에서 사용하거나 block 단위로 사용할 수 있다.
- 임계 구역(Critical Section)을 설정하여 한 번에 하나의 스레드만 접근 가능하는 것이다. (한 번에 하나의 스레드만 임계 구역에 접근할 수 있도록 하여 데이터 일관성을 유지한다.)
- Java에서 synchronized는 모니터를 사용하여 구현되기 때문에, 모든 객체는 Monitor lock을 가지며 synchronized는 해당 객체의 락을 획득하여, 다른 스레드가 접근하지 못하도록 한다. (synchronized는 해당 객체의 모니터를 잠그는 역할)
- Blocking 방식이므로, 락을 기다리는 동안 스레드가 작업을 멈춘다.
- 따라서, 한 스레드가 lock을 얻어서 임계 영역에 접근하면 해당 영역에 접근하고 싶은 모든 스레드가 blocking이 걸려 아무 일도 못하며 스레드 상태를 변경하는 비용때문에 성능이 좋지 않다.
| atomic
java.util.concurrent.atomic 패키지로, *CAS(Compare And Swap) 알고리즘을 사용해 non-blocking으로 원자성 문제를 해결한다.
- 작업이 다른 스레드의 개입 없이 한 번에 이루어지는 최소 단위를 atomic이라고 한다.
- atomic 연산자는 해당 작업이 동시성 문제 없이 안전하게 실행된다는 특징이 있다.
- 대표적으로 변수 할당이 있다.
- atomic 타입은 멀티 스레드 환경에서 원자성을 보장하기 위한 개념이다.
*CAS : 병렬 프로그래밍에서 동시성 문제를 해결하기 위해 사용하는 비차단(non-blocking) 알고리즘. 공유 자원을 안전하게 업데이트하기 위해, 현재 값과 예상 값(expected value)을 비교한 후, 예상 값이 일치하면 새로운 값으로 교체(swap)한다.
'💻 개발 > Java' 카테고리의 다른 글
동시성 프로그래밍 - 심화 (0) | 2024.12.19 |
---|---|
JCF와 스레드 - JCF 심화 및 스레드 (0) | 2024.12.05 |
JCF와 스레드 - JCF 기초 (0) | 2024.12.04 |
Java 모의 면접 후기 (0) | 2024.12.01 |
11/25 - TIL : JPA와 N+1 (0) | 2024.11.25 |