본문 바로가기
Back-End/강의

[재고시스템으로 알아보는 동시성이슈 해결방법] 2. Application Level에서의 해결법

by 7533ymh 2022. 8. 26.

동시성 이슈 해결방법

  • 이번 시간에는 Application Level에서의 해결방법을 알아보자.

해결법1. Synchronized

  • Java에서 지원하는 방법 ( 어플리케이션 단 )
  • 해당 키워드는 한 개의 스레드만 접근이 가능하도록 해준다.
@Transactional
public synchronized void decrease(Long id, Long quantity) {
    Stock stock = stockRepository.findById(id)
        .orElseThrow(() -> new IllegalArgumentException("재고가 존재하지 않습니다."));

    stock.decrease(quantity);

    stockRepository.saveAndFlush(stock);
}

synchronized를 추가하고 테스트를 돌려보면?


그래도 실패한다…..

왜 그럴까?

이유는 스프링의 @Transactional에 있다.

스프링의 트랜잭션 어노테이션은 AOP이므로 프록시를 통해 프록시 객체를 만들어서 수행하게 된다.

대략적으로 아래와 같은 모습으로 진행하게 된다.

/*
프록시 객체
 */
public class TransactionStockService {

    private final StockService stockService;

    public TransactionStockService(StockService stockService) {
        this.stockService = stockService;
    }

    public void decrease(Long id, Long quantity) {
        startTransaction();

        stockService.decrease(id, quantity);

        endTransaction();
    }

    private void startTransaction() {
        // 트랙잭션 시작
    }

    private void endTransaction() {
        // 트랙잭션 끝 ( 커밋 )
    }
}
  • 여기서 문제점이 decrease 메서드는 synchronized처리를 통해 한 스레드만 접근할 수 있지만 decrease매서드가 끝남과 동시에는 다른 스레드가 접근할 수 있다.
  • 즉, A라는 스레드가 decrease 메서드를 끝낸 후 endTransaction메서드를 수행하려 할 때 B 스레드는 decrease 메서드에 접근할 수 있게 되고 앞써 본 문제점이 동일하게 나타나 테스트가 실패하게 되는 것이다.

해결하려면?

  • 간단하다. @Transactional을 없애면 된다!
  • 그렇게 되면 decrease 메서드는 무조건 한 스레드만 접근할 수 있게 되니 동시성 문제가 사라지게 된다.

  • 테스트도 통과한 모습

@Transactional이 없어도 동작하는 이유

  • 처음에 강의에서 더티 체킹을 하지 않고 saveAndFlush메서드로 업데이트하길래 왜 그런가 생각했다.
  • 알고보니 해당 예제를 보여주기 위해서 인 것 같다.
  • 그럼 트랜잭션 어노테이션이 없더라도 업데이트가 되는 이유가 뭘까?
  • 사실 JpaRepositorysaveAndFlush메서드는 자체적으로 @Transactional을 가지고 있다.
    • 구현체인 SimpleJpaRepository에서 확인할 수 있다.
    • save 메서드도 마찬가지!
    • save에 어노테이션이 붙어있는 데 saveAll에도 붙어있는 이유는 self invocation때문이다.
  • 그렇기에 더티체킹으로 할 경우 테스트는 실패하게 된다.

문제점

  • 단일서버일 때는 문제가 되지 않는다.
  • 만약 다중 서버라면?
    • synchronized는 각 인스턴스안에서만 thread-safe가 보장이 된다.
    • 그렇기 때문에 다중 서버일땐 레이스 컨디션이 발생하게 된다.
      • 위의 문제점에서 A,B Thread를 A,B 서버로 생각하면 된다.
  • 요즘 서비스는 다중 서버를 사용하기 때문에 synchronized는 거의 사용되지 않는다.

결론

  • 자바에서 지원하는 Synchronized 키워드를 통해 어플리케이션 단에서 쉽게 동시성 문제를 해결할 수 있다.
  • 하지만 해당 문제는 단일 서버에서의 해결방법일뿐 다중 서버에서는 해결되지 않는다.

Reference

댓글