본문 바로가기
카테고리 없음

자바 멀티스레딩 사례 (락, CAS, AQS)

by info95686 2025. 10. 20.

자바 멀티스레딩 사례
자바 멀티스레딩 사례

자바에서 멀티스레딩은 단순히 속도를 높이기 위한 보너스가 아니라, 서버·백엔드·배치·게임·금융 등 여러 도메인에서 필수 역량이다. 특히 동기화 전략을 제대로 고르지 못하면 교착상태, 기아, 스루풋 저하, 예측 불가능한 지연 같은 문제가 즉시 드러난다. 이 글은 실무 시나리오를 통해 락(lock) 기반 설계, CAS(Compare-And-Set) 중심의 락-프리 패턴, 그리고 AQS(AbstractQueuedSynchronizer) 계열 동기화 도구의 실제 사용을 비교하고, 언제 무엇을 선택해야 하는지 판단 기준을 제시한다.

락: 재진입 락과 조건 대기로 푸는 실무 동시성 문제

락은 가장 이해하기 쉬운 동기화 도구다. 웹 주문 처리, 금융 거래처럼 “정합성이 절대적”인 구간에서는 락으로 임계 구역을 간명하게 둘러싸는 것이 최우선이다. 실무에서 흔한 패턴은 두 가지다. 첫째, 재진입 가능 락(ReentrantLock)으로 임계 구역을 보호하고, 실패 가능성이 있거나 외부 I/O가 섞인 코드는 꼭 임계 구역 밖으로 빼 병목을 줄인다. 예컨대 재고 감소는 락 안에서 수행하되, 결제 API 호출이나 로그 적재, 메시지 브로커 전송은 락 밖에서 처리해 동시성 수준을 유지한다. 둘째, 조건 변수(Condition)로 생산자–소비자 큐를 만든다. 생산자는 버퍼가 찰 때까지 await로 대기하고, 소비자는 처리 후 signalAll로 생산자를 깨운다. 이때 반드시 while 루프로 조건을 재검증해야 스퍼리어스 웨이크업과 경합 상황을 방지할 수 있다. 스레드풀과의 조합도 중요하다. 락 경쟁이 심한 작업을 고정 크기 풀에 넣으면 대기열이 길어지고 전체 지연이 폭증한다. 반대로 락 범위를 좁히고 데이터 파티셔닝(키 기반 샤딩)을 적용하면 각 샤드별로 락 경쟁이 줄어들어 코어 수에 비례한 처리량이 가능해진다. 또 읽기 많은 워크로드에선 ReentrantReadWriteLock으로 읽기 병렬성을 올리고, 캐시와 같이 읽기 우세 패턴에서는 StampedLock의 낙관적 읽기를 통해 낮은 오버헤드로 일관성을 확보하기도 한다. 단, StampedLock은 재진입 불가이고 인터럽트 응답성이 떨어질 수 있으니 “짧은 임계 구역 + 높은 읽기 비율”에만 제한적으로 적용한다. 실무 체크리스트는 다음과 같다. 임계 구역 최소화, 락 안에서 I/O 금지, 조건 대기는 while 검증, 타임드 락 시도(tryLock)로 교착 회피, 메트릭으로 락 대기 시간 관측.

CAS: 락-프리로 지연을 낮추는 원자적 갱신 패턴

CAS는 “비교 후 같으면 교체”라는 단일 원자 연산을 이용해 락 없이 공유 상태를 갱신한다. 자바에서는 Atomic* 클래스(AtomicInteger, AtomicLong, AtomicReference 등)로 손쉽게 사용할 수 있다. 실무 적용 포인트는 세 가지다. 카운터·시퀀스·레이트리밋처럼 단순 수치 갱신은 CAS가 최적이다. 고부하 환경에서 synchronized나 ReentrantLock 대비 대기열이 생기지 않아 p99 지연이 안정적으로 떨어진다. Lock-Free 스택/큐/슬롯 교체처럼 참조 스왑이 필요한 경우 AtomicReference의 compareAndSet로 헤드 포인터를 갱신한다. 단, ABA 문제가 발생할 수 있으므로 스탬프(AtomicStampedReference)나 버전 필드를 병행해 “같은 참조가 잠깐 빠졌다 다시 들어온” 경우를 구분해야 한다. 또한 CAS 루프의 공평성·수렴성 관리를 위해 백오프, 파티션, 배치 업데이트를 적용해 실패 확률을 낮춘다. 여러 필드를 함께 일관적으로 다뤄야 한다면 스냅샷 구조(불변 객체 교체)나 상위 동기화를 고려한다. CAS는 “작고 빈번한 갱신, 충돌 가능성 낮음, 지연 우선”에서 효과적이다.

AQS: 큐 기반 동기화 도구로 공정성과 확장성 확보

AQS(AbstractQueuedSynchronizer)는 ReentrantLock, Semaphore, CountDownLatch, CyclicBarrier, Phaser 등 다양한 동기화 도구의 기반이다. 대표 사례는 다음과 같다. 요청 허용량 제어에서 Semaphore로 동시 처리 상한을 두고, 초과 시 대기 또는 즉시 실패로 백프레셔를 건다. 단계 동기화는 CountDownLatch로 “모두 완료될 때까지 대기”를 구현하고, 반복 라운드는 CyclicBarrier나 Phaser를 사용한다. 스레드풀 제출 조절에도 활용 가능하며, 필요하면 AQS를 상속해 커스텀 동기화를 만들 수 있다. 장점은 대기 큐로 스핀 낭비를 줄이고, 타임드 획득/인터럽트 대응이 쉬우며, 공정 모드로 기아를 낮출 수 있다는 점이다. 단, 과도한 공정성은 처리량을 떨어뜨릴 수 있고, 긴 보유 시간은 대기열을 키운다. 따라서 공정성은 필요한 곳에만, 보유 시간은 짧게, 획득 실패 시 폴백 경로(큐잉·리트라이·서킷브레이크)를 준비해야 한다. 운영 지표로는 락 대기 시간, 대기열 길이, 타임아웃 비율을 추적한다.

 

결론: 선택 기준과 실무 체크리스트

락은 정합성과 단순함, CAS는 지연 최소화와 경량 갱신, AQS는 공정한 대기와 제어된 백프레셔에 강점을 가진다. 설계 단계에서 데이터 파티셔닝, 임계 구역 최소화, 타임드 시도와 폴백 경로를 기본값으로 두면 예측 가능한 지연과 안정적인 처리량을 확보할 수 있다. 동시성의 해법은 하나가 아니라, 워크로드 특성과 실패 모드에 맞춘 적절한 조합이라는 점을 기억하자.