728x90
반응형
쿠폰 재고가 1개일 때 100명이 동시에 발급 요청을 보내도 쿠폰이 1개만 발급되는지 검증하고 싶었다.
핵심 개념
ExecutorService
여러 작업을 스레드로 실행하고 관리하는 도구다.
ExecutorService executor = Executors.newFixedThreadPool(100);
100개의 작업을 동시에 실행할 수 있는 스레드 풀을 만든다.
executor.submit(() -> couponService.issueCoupon());
submit()을 호출하면 전달한 작업이 스레드 풀에서 실행된다.
CountDownLatch
내부 카운터가 0이 될 때까지 스레드를 기다리게 하는 동기화 도구다.
CountDownLatch latch = new CountDownLatch(100);
카운터가 100에서 시작한다.
latch.countDown();
호출할 때마다 카운터가 1씩 감소한다.
latch.await();
카운터가 0이 될 때까지 현재 스레드가 기다린다.
테스트에서 사용하는 세 가지 Latch
CountDownLatch readyLatch = new CountDownLatch(100);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(100);
각 역할은 다음과 같다.
Latch역할
| readyLatch | 100개 작업이 모두 출발 준비를 마쳤는지 확인 |
| startLatch | 대기 중인 작업을 한꺼번에 출발시키는 신호 |
| doneLatch | 100개 작업이 모두 종료됐는지 확인 |
예제 코드
@Test
void 쿠폰은_동시에_요청해도_한_개만_발급된다() throws Exception {
int requestCount = 100;
ExecutorService executor =
Executors.newFixedThreadPool(requestCount);
CountDownLatch readyLatch =
new CountDownLatch(requestCount);
CountDownLatch startLatch =
new CountDownLatch(1);
CountDownLatch doneLatch =
new CountDownLatch(requestCount);
List<Future<Boolean>> results = new ArrayList<>();
for (int i = 0; i < requestCount; i++) {
Future<Boolean> result = executor.submit(() -> {
try {
readyLatch.countDown();
startLatch.await();
return couponService.issueCoupon();
} finally {
doneLatch.countDown();
}
});
results.add(result);
}
readyLatch.await();
startLatch.countDown();
doneLatch.await();
long successCount = results.stream()
.filter(result -> {
try {
return result.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.count();
assertThat(successCount).isEqualTo(1);
assertThat(couponService.getIssuedCount()).isEqualTo(1);
executor.shutdown();
}
실행 흐름
100개 쿠폰 발급 작업 제출
↓
각 작업이 readyLatch를 감소
↓
모든 작업이 startLatch 앞에서 대기
↓
readyLatch가 0이 되면 모두 준비 완료
↓
startLatch.countDown()으로 동시 출발
↓
100개 작업이 쿠폰 발급 요청
↓
doneLatch가 0이 될 때까지 대기
↓
성공 건수와 최종 발급 수량 검증
배운 점
ExecutorService만 사용하면 여러 작업을 실행할 수는 있지만, 모든 작업이 같은 시점에 시작된다고 보장하기 어렵다.
CountDownLatch를 함께 사용하면 모든 작업을 출발선에 대기시킨 뒤 한꺼번에 실행할 수 있어, 동시성 충돌 상황을 더 확실하게 재현할 수 있다.
실제 서비스에서는 synchronized뿐 아니라 DB 락, 유니크 제약 조건, 조건부 업데이트 등을 사용해 정합성을 보장해야 한다.
반응형
'Study' 카테고리의 다른 글
| TIL - JVM Cold Start: 왜 첫 요청은 항상 느린가 (0) | 2026.08.07 |
|---|---|
| TIL - HikariCP 커넥션 풀과 Cold Start (0) | 2026.07.24 |
| TIL - MSA에서 HTTP vs Kafka, Spring Cloud Gateway (0) | 2026.07.23 |
| TIL - Docker restart 정책 비교 (no / on-failure / always / unless-stopped) (0) | 2026.07.22 |
| TIL - Boolean.TRUE.equals()를 쓰는 이유 (0) | 2026.07.21 |