락을 걸지 않고 원자적인 연산을 수행할 수 있는 방법이 있는데 이것을 CAS(Campare-And-Swap) 연산이라 합니다.
이 방법은 락을 사용하지 않기 때문에 Lock-Free 기법이라합니다.
CAS 연산은 락을 완전히 대체하는 것은 아니고 작은 단위의 일부 영역에 적용할 수 있습니다.
기본은 락을 사용하고 특별한 경우에 CAS를 적용할 수 있다고 생각하면 됩니다.
안전한 임계 영역이 필요하지만 연산이 길지 않고 아주 짧게 끝날때 사용해야한다.
단순히 숫자 값의 증가, 자료 구조의 데이터 추가와 같이 CPU 싸이클이 금방끝나는 연산에 사용하면 효과적이다.
반면에 데이터베이스를 기다린다거나, 다른 서버의 요청을 기다리는 것 처럼 수 밀리초 이상의 시간이 걸리는 작업이라 면 CAS를 사용하는 것 보다 동기화 락을 사용하거나 스레드가 대기하는 방식이 더 효과적이다.
public class SpinLock {
private volatile AtomicBoolean lock = new AtomicBoolean(FALSE);
public void lock() {
log("Lock 획득 시도");
while (!lock.compareAndSet(FALSE, TRUE)) {
log("락 획득 실패 - 스핀 대기");
}
log("락 획득 완료");
}
public void unlock() {
lock.set(FALSE);
log("락 반납 완료");
}
}
CAS를 활용한 원자적인 연산은 스레드가 쪼갤 수 없어 여러 스레드가 동시에 실행해도 안전하다.
원자적으로 만든 락 덕분에 무거운 동기화 작업 없이 가벼운 락을 만들 수 있다.
동기화 락을 사용하는 경우 스레드가 락을 획득하지 못하면 BLOCKED, WAITING 등 상태가 변한다.
또한 대기상태의 스레드를 깨워야 하는 무겁고 복잡한 과정이 추가로 들어간다.
CAS를 활용한 락 방식은 사실 락이 없다. 단순히 while문을 반복할 뿐이다. 따라서 대기하는 스레드도 RUNNABLE 상태를 유지하면서 가볍고 빠르게 동작한다.
하지만 while문을 반복하면 락을 기다리는 스레드가 CPU를 계속 사용하면서 대기하게 된다.
Reference:
- 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성