https://computerlove.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C-1
- 데이터에 한 개의 스레드만 접근 가능하게 하면 된다!
- 자바에서 synchronized를 활용하면 손쉽게 한 개의 스레드만 접근이 가능하도록 할 수 있다
- synchronized를 메서드 선언부에 붙여주게 된다면 해당 메서드는 한 개의 스레드만 접근이 가능
StockService
@Service
public class StockService {
@Autowired
private StockRepository stockRepository;
@Transactional
public synchronized void decrease(Long id, Long quantity){
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
StockServiceTest
@Test
public void 동시에_100개씩_요청() throws InterruptedException {
int threadCount = 100;
// 멀티 스레드를 이용하는 자바의 API 비동기
ExecutorService executorService = Executors.newFixedThreadPool(32);
// 100개의 요청이 끝날 때까지 기다려야 하므로
// 다른 스레드에서 수행중인 작업이 완료될 때까지 대기할 수 있도록 도와주는 클래스
CountDownLatch latch = new CountDownLatch(threadCount);
for(int i = 0; i < threadCount; i ++){
executorService.submit(()->{
try {
stockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertThat(stock.getQuantity()).isEqualTo(0);
}
- 테스트 케이스가 실패..
- 스프링의 Transactional의 동작 방식 때문!
- 스프링에서는 Transactional 어노테이션을 이용하면 우리가 만든 클래스를 래핑 하는 클래스를 새로 만들어서 실행한다
예시)
public class TransactionStockService {
private StockService stockService;
public TransactionStockService(StockService stockService){
this.stockService = stockService;
}
public void decrease(Long id, Long quantity){
startTransaction();
stockService.decrease(id, quantity); // 10 : 00
// 10 : 00 ~ 10:05
endTransaction(); // 10 : 05
}
private void startTransaction() {
}
private void endTransaction() {
}
}
- StockService를 필드로 가지는 클래스를 새로 만들어서 실행한다
- stockService의 decrease 메서드가 호출이 될 때 트랜잭션을 시작하고
- stockService의 decrease 메서드를 호출한 후에 정상적으로 종료가 되면 그 이후에 트랜잭션을 종료합니다.
- 트랜잭션 종료 시점에 데이터베이스에 업데이트를 하고 여기서 문제가 발생한다
- decrease 메서드가 완료가 되었고 실제 데이터베이스에 업데이트하기 전에 다른 스레드가 decrease 메서드를
- 호출할 수 있기 때문에 다른 스레드는 갱신되기 전의 값을 가져가서 이전과 동일한 문제가 발생하게 되는 것!
- 이해하기 쉽게 예시를 들어서 설명을 하면 10:00에 decrease 메서드가 종료가 되었고,
- 트랜잭션 종료를 10:05 즉 업데이트를 10:05분에 수행한다
- 그러면 10시부터 10시 5분까지 다른 스레드가 decrease 메서드로 호출할 수 있게 된다
- 그러면 다른 스레드는 갱신되기 이전의 값을 가져가서 이전과 동일한 문제가 발생하는 것이다
- 해결하는 방법으로는 여러 가지가 있겠지만 예제에서는 @Transactional 어노테이션을 주석 처리하고 테스트 케이스를 다시 실행한다
- 테스트 케이스가 정상적으로 성공
- synchronized 를 활용해도 문제가 생기는 부분을 다음에 포스트 할 예정
'CS지식들 > 공부공부' 카테고리의 다른 글
Spring Security filterChain (0) | 2023.03.06 |
---|---|
동시성 문제 (3) (0) | 2022.12.19 |
동시성 문제 (1) (0) | 2022.12.18 |
JPA에서 페이징/정렬 처리하기 (0) | 2022.11.30 |
Mybatis와 스프링에서 페이징 처리 (0) | 2022.11.30 |