백엔드 엔지니어 이재혁
[JAVA] CAS 락 구현 본문
CAS 연산을 활용해 동기화 락 (synchronized, Lock 인터페이스) 없이 락을 구현해보자.
우선 잘못 만든 예제를 확인해보자.
public class SpinLockBad {
private volatile boolean lock = false;
public void lock() {
log("락 획득 시도");
while (true) {
if (!lock) { // 1. 락 사용 여부 확인
// sleep(100); // 문제 상황 확인용, 스레드 대기
lock = true; // 2. 락의 값 변경
break; // while 탈출
} else {
// 락을 획득할 때까지 스핀 대기(바쁜 대기) 한다.
log("락 획득 실패 - 스핀 대기");
}
}
log("락 획득 완료");
}
public void unlock() {
lock = false; // 락 반납
}
}
위 구현은 어떤 문제가 있을까?
- 락 사용 여부 확인
- 락의 값 변경
이 두 과정 사이에 다른 스레드가 개입할 수 있는 여지가 있으면 안된다!
이럴 때 CAS 연산을 떠올릴 수 있다면 좋은 해결책임을 알 수 있다.
락의 현재 상태를 확인하는 것과 그 값을 변경하는 것을 동시에 할 수 있다.
SpinLock
lock() 메서드를 다음과 같이 개선할 수 있다.
public void lock() {
log("락 획득 시도");
while (!lock.compareAndSet(false, true)) {
// 락을 획득할 때까지 스핀 대기(바쁜 대기) 한다.
log("락 획득 실패 - 스핀 대기");
}
log ("락 획득 완료");
}
CAS 연산을 활용해 다음 두 연산을 하나의 원자적 연산으로 바꿨다!
- 락 사용 여부 확인: lock의 값이 false면
- 락의 값 변경: lock의 값을 true로 변경해라
위 방식으로 작동하는 lock이 SpinLock이다. 락 획득을 실패해도 while문을 계속 돌아가는 것을 보고 Spin이라고 한다. 락 획득에 실패해도 CPU 자원을 계속 소모하기 때문에 락 획득 대기 시간이 길어지면 안된다. (충돌 가능성이 낮아야 한다.)
CAS를 활용할 수 있는 순간을 떠올리기
Javascript로 프로그래밍을 할 때 다음과 같은 방식으로 코드를 짰던 것이 생각났다.
if (num++) {
// run
}
Javascript에서는 상태 확인과 그 값을 새로 대입하는 과정을 if 조건 안에 한 번에 넣을 수 있다. 이런 경우에 CAS 연산을 생각해볼 수 있을 것 같다. (그렇다고 위 조건문이 원자적 연산으로 돌아간다는게 절대 아니다. 만약 Javascript가 멀티스레딩 환경으로 돌아간다면 문제가 생길 수 있다.)
"상태 확인과 값 변경"을 원자적으로 처리해볼 수 있는 순간을 떠올리기 위한 아이디어로 기억해두자.
CAS vs 동기화 락 장단점 정리
| CAS | 동기화 락 | |
| 장점 | 락 프리(Lock-Free) 병렬 처리가 더 효율적이게 될 수 있다 |
충돌 관리, 안정성: 복잡한 상황에서도 일관성 있는 동작 스레드 대기: 락을 대기하는 스레드는 CPU를 거의 사용하지 않음 |
| 단점 | 충돌이 빈번한 경우 충돌 시 반복적인 재시도 자주 반복되면 성능 저하 |
락 획득 대기: 락 획득을 위한 대기 시간이 길어질 수 있다 컨텍스트 스위칭 오버헤드: 스레드 상태 변경으로 인한 오버헤드 |
'Java' 카테고리의 다른 글
| [Java] Executor 프레임워크 기본 (0) | 2025.06.25 |
|---|---|
| [Java] 컬렉션 프레임워크와 동시성 (0) | 2025.06.13 |
| [Java] CAS 연산 활용 (1) | 2025.06.09 |
| [Java] Atomic과 CAS 연산 (0) | 2025.06.09 |
| [Spring] Spring의 주입 방식 (0) | 2025.06.09 |