** 본 글은 인프런 <재고시스템으로 알아보는 동시성이슈 해결방법> 을 수강한 후 작성한 글입니다.
동시성 문제 해결 방법 1. Synchronized
Synchronized
자바의 Synchronized
를 이용해보자.
Synchronized
를 메서드 선언부에 붙여주면 해당 메서드는 한 개의 스레드만 접근이 가능하게 된다.
@Transactional
public synchronized void synchronizedDecrease(Long id, Long quantity) {
// Stock 조회
Stock stock = stockRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Stock을 조회할 수 없습니다."));
// 재고 감소
stock.decrease(quantity);
// 갱신된 값 저장
stockRepository.saveAndFlush(stock);
}
@Test
void 동시에_100개의_요청_synchronized() throws InterruptedException {
// when
int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i=0; i<threadCount; i++) {
executorService.submit(() -> {
try {
stockService.synchronizedDecrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
// then
Stock stock = stockRepository.findById(1L).get();
assertEquals(0, stock.getQuantity());
}
이제 100개의 수량이 모두 감소했을까?
실패. 왜 예상 값과 결과 값이 다를까? 이유는 @Transactional
때문!
트랜잭션을 시작한 이후 메서드를 호출하고 메서드 실행이 종료가 되면 트랜잭션을 종료하는데, 이때 트랜잭션 종료 시점에 데이터베이스에 업데이트를 한다.
재고 감소 메서드가 완료가 되었고, 실제 데이터베이스가 업데이트 되기 전에 다른 스레드가 해당 메서드에 접근을 했기 때문에 갱신 되기 전의 값을 가져가서 문제가 발생하게 된 것이다.
트랜잭션 처리가 되어 있지 않을 경우, 테스트가 성공적으로 작동한다.
문제점
Java의 Synchronized는 하나의 프로세스 안에서만 보장이 된다. 결국 여러 프로세스에서는 서로 다른 스레드에서 같은 데이터에 접근을 할 수 있게 되면서 문제가 발생하게 된다.
따라서 실제 시스템에서는 Synchronized는 거의 이용하지 않는다.
'* > Spring' 카테고리의 다른 글
[Spring Boot] 동시성 제어 (4) - Redis (1) | 2024.11.12 |
---|---|
[Spring Boot] 동시성 제어 (3) - Database (1) | 2024.11.11 |
[Spring Boot] 동시성 제어 (1) - 동시성 문제, 재고 감소 시스템 구현 및 테스트 (1) | 2024.11.09 |
[JPA] 엔티티 equals 메서드 구현 시 주의할 점 (0) | 2024.11.08 |
[JPA] Join Fetch 시 MultipleBagFetchException (0) | 2024.11.07 |