원자적 연산
해당 연산이 더 이상 나눌 수 없는 단위로 수행된다는 것
원자적 연산은 중단되지 않고, 실행되거나 실행되지 않는 성질을 가진다
멀티 쓰레드 환경에서는 다른 쓰레드의 간섭 없이 안전하게 처리됨을 의미한다
- 원자적 연산
- int i = 0;
- 원자적 연산 X
- i += 1;
- i++;
원자적 연산이 아니라면 synchronized나 Lock 등을 이용해 안전한 임계 영역을 만들어야 한다
AtomicInteger 등 Atomic~
멀티 쓰레드 환경에서 안전하게 증감 연산을 수행할 수 있게 해주는 객체
snychronized, ReentrantLock 보다 빠르다
그 이유는 incrementAndGet() 메서드는 락을 사용하지 않고 원자적 연산을 수행하기 때문
https://github.com/venzersiz/learn-java8/tree/master/src/test/java/concurrency/atomic
CAS
우리가 직접 CAS 연산을 사용하는 경우는 드물다. 복잡한 동시성 라이브러리들이 CAS 연산을 통해 최적화하기 때문이다
락 기반 방식의 문제점
synchronized, Lock, ReentrantLock을 사용하는 경우를 말함
락을 얻고 반납하는 과정이 반복된다. 직관적이지만 상대적으로 무겁다
CAS
Compare And Swap/Set 연산
락을 걸지 않고 원자적인 연산을 수행할 수 있다. Lock free 기법이라고 한다
락을 완전히 대체하는 것은 아니고 작은 단위의 일부 영역에 적용할 수 있다
기본적으로 락을 사용하고 특별한 경우에만 CAS를 적용하도록 한다
Atomic~ 클래스의 compareAndSet 메서드는 원자적으로 실행된다
CAS 연산은 메모리에 있는 값이 기대하는 값이라면 원하는 값으로 변경한다
atomicInt.compareAndSet(0, 1);
- CPU 코어
- 메모리의 값 확인
- 기대하는 값이라면 원하는 값으로 변경
위 명령은 원자적이지 않은 것으로 보이지만, 원자적으로 동작한다
CPU 하드웨어의 지원
CAS 연산은 CPU가 하드웨어적으로 원자적으로 처리하는 것을 말한다. 현대 CPU들은 CAS 연산을 위한 명령어를 제공한다
스레드 충돌
스레드가 동시에 실행되면서 문제가 발생하는 것을 스레드가 충돌했다고 말한다
충돌이 발생할 때마다 반복해서 다시 시도하므로 결과적으로 락 없이 데이터를 안전하게 변경할 수 있다
충돌이 드물게 발생하는 환경에서는 높은 성능을 발휘할 수 있지만, 충돌이 빈번하게 발생하는 환경에서는 성능에 문제가 될 수 있다
여러 스레드가 자주 동시에 동일한 변수값을 변경하려고 하면 CPU 자원을 많이 소모하게 된다
두 방식의 비교
- Lock
- 비관적 접근법
- 다른 스레드가 방해할 것이라고 가정
- 데이터 접근 전 락을 얻는다
- 다른 스레드의 접근을 막음
- 비관적 접근법
- CAS
- 낙관적 접근법
- 대부분 충돌이 없을 것이라고 가정
- 락을 사용하지 않음
- 충돌 발생 시 재시도
- 낙관적 접근법
간단한 CPU 연산은 너무 빨리 처리되어 충돌이 자주 발생하지 않는다(그 전에 연산이 완료된다)
결국 간단한 CPU 연산에는 CAS를 사용하는 것이 효과적이다
CAS를 이용한 락 구현
CAS 연산을 사용해 원자적인 연산을 하게 되면 상대적으로 무거운 동기화 작업 없이 가벼운 락을 만들 수 있다
동기화 락을 사용하면 스레드가 락을 획득하지 못하면 BLOCKED, WAITING 상태가 된다. 또 대기 상테의 스레드를 깨워야 하는 무겁고 복잡한 과정이 추가로 들어간다. 따라서 성능이 상대적으로 느릴 수 있다. 반면 CAS를 활용한 락 방식은 사실 락이 없다. 단순히 반복할 뿐이다. 대기 스레드는 RUNNABLE 상태를 유지한다
단점
장점이 단점이 된다. 락을 기다리는 스레드가 CPU를 계속 사용하게 된다
안전한 임계 영역이 필요하지만, 연산이 길지 않고 매우 짧게 끝날 때 사용해야 한다
- 숫자 값의 증가
- 자료구조의 데이터 추가
반면 외부 시스템의 결과를 기다리는 등의 오래 기다려야 하는 작업에는 최악의 결과가 나올 수 있다
Spin lock
락이 해제되기를 기다리면서 반복문을 통해 계속해서 확인하는 방식
마치 제자리에서 Spin하는 것 같다고 하여 명명
스레드가 락을 얻으려고 대기하는 것을 Spin wait, 바쁘게 대기한다고 해서 Busy wait이라고도 한다
쓰레드를 대기(WAITING, BLOCKED)시키지 않는 락이다
https://github.com/venzersiz/learn-java8/tree/master/src/test/java/concurrency/cas
'Java > Concurrency' 카테고리의 다른 글
| Java > Concurrency > 10. Thread pool, Executor framework (0) | 2024.10.14 |
|---|---|
| Java > Concurrency > 9. Concurrent Collections (0) | 2024.10.14 |
| Java > Concurrency > 7. Synchronization (0) | 2024.09.23 |
| Java > Concurrency > 6. Memory visibility (0) | 2024.09.13 |
| Java > Concurrency > 3. Thread info (0) | 2024.09.05 |