👨🏻💻 참고강의
프로젝트를 하면서 발생할 수 있는 동시성 문제에 대해서 알아보고, 어떻게 해결할 수 있는지 알아보기 위해 글을 작성한다. [자료는 위의 강의를 참고하였습니다.]
여러 자원에 대해 프로세스 또는 스레드가 동시에 접근하면서 발생하는 문제
그렇다면 어떤 경우에 동시성 문제가 발생할 수 있을까?
@Service
@RequiredArgsConstructor
public class StockService {
private final StockRepository stockRepository;
@Transactional
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.save(stock);
}
}
// 요청이 1번만 들어오는 경우
@Test
public void decreaseStock() {
stockService.decrease(1L, 1L);
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(99, stock.getQuantity());
}
// 한 아이디를 대상으로 동시에 N(100) 개의 요청을 보낸다면?
@Test
public void sameRequest() throws InterruptedException {
int threadCount = 100;
// ExecutorService : 비동기로 실행하는 작업을 단순화하여 사용할 수 있도록 도와주는 자바의 API
ExecutorService executorService = Executors.newFixedThreadPool(32);
// CountDownLatch : 다른 스레드에서 수행중인 작업이 완료될 때 까지 대기할 수 있도록 도와주는 클래스
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i=0; i<100; i++) {
executorService.submit( () -> {
try {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0, stock.getQuantity());
}
이와 같이 1이라는 id 를 가진 상품을 동시에 100번 요청하는 경우, 당연하게도 모든 처리가 완료되면 재고는 0개가 남을 것 이라고 생각했다. 하지만 테스트 코드에서는(나의 경우) 여전히 89개가 남아있는 것을 확인할 수 있었다.
그렇다면 이유는 무엇일까?
둘 이상의 스레드가 공유 데이터에 엑세스할 수 있고, 동시에 변경을 하려고 할 때, 발생할 수 있는 문제
테스트 코드에서 위와 같은 결과가 발생하는 이유는 Race Condition 이 발생하였기 때문이다. 어떻게 이러한 결과가 나오는지 표로 살펴보자.
해결 방법은 다음 포스트 에서 알아보자