[Spring] 동시성 문제(1)

Kyungmin·2024년 6월 19일
0

Spring

목록 보기
22/39
post-thumbnail

👨🏻‍💻 참고강의
프로젝트를 하면서 발생할 수 있는 동시성 문제에 대해서 알아보고, 어떻게 해결할 수 있는지 알아보기 위해 글을 작성한다. [자료는 위의 강의를 참고하였습니다.]

1. 동시성 문제

여러 자원에 대해 프로세스 또는 스레드가 동시에 접근하면서 발생하는 문제

그렇다면 어떤 경우에 동시성 문제가 발생할 수 있을까?

StockService

  • 해당하는 상품 ID 의 재고를 감소시키는 서비스 로직
@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);

    }
}

StockServiceTest

  • 요청이 한 번만 들어오는 경우
// 요청이 1번만 들어오는 경우
@Test
public void decreaseStock() {
    stockService.decrease(1L, 1L);

    Stock stock = stockRepository.findById(1L).orElseThrow();

    assertEquals(99, stock.getQuantity());
 }
  • 요청이 여러 번 들어오는 경우 (100번 테스트)
// 한 아이디를 대상으로 동시에 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개가 남아있는 것을 확인할 수 있었다.

그렇다면 이유는 무엇일까?

2. Race Condition

둘 이상의 스레드가 공유 데이터에 엑세스할 수 있고, 동시에 변경을 하려고 할 때, 발생할 수 있는 문제

테스트 코드에서 위와 같은 결과가 발생하는 이유는 Race Condition 이 발생하였기 때문이다. 어떻게 이러한 결과가 나오는지 표로 살펴보자.

1) 예상 작업 순서

  • 예상 작업 순서는 위와 같다. 스레스1 에서 id=1 인 재고를 선택하고 수량을 업데이트한다. 그러고 나서 스레드2가 같은 작업을 수행하면서 옳바르게 수량을 감소시킨다.

2) 실제 작업 순서

  • 스레드1 에서 조회를 한다.
    이어서 바로 스레드2 에서 같은 id 로 조회를 한다.
    스레드1은 스레드2가 조회된 이후에 재고를 감소한다.(재고=4)
    그리고 스레드2가 재고를 감소한다.
    스레드2는 수량이 5일때 조회를 하였으므로, 감소를 시키면 여전히 재고가 4이다.(재고=4)
    결국,모든 작업이 완료된 이후, 원하는 결과인 3이 아니라 재고는 4가 남게된다.(문제 발생)

해결 방법은 다음 포스트 에서 알아보자

profile
Backend Developer

0개의 댓글

관련 채용 정보