Race Condition
Race Condition
은 여러 개의 프로세스 또는 스레드가 공유 자원에 동시 접근(변경) 할 때synchronized
키워드를 메서드 선언부에 붙여, 해당 메서드는 하나의 스레드만 접근 가능하도록 함synchronized
는 자바에서 멀티스레드 환경에서 동기화(synchronized)를 보장하기 위한 키워드📂 StockService.java
@Transactional
public synchronized void decreaseStock(Long id, Long quantity) {
Stock stock = stockRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 id입니다."));
stock.decreaseQuantity(quantity);
stockRepository.saveAndFlush(stock);
}
synchronized
키워드를 사용해 문제를 해결하려고 했으나, 테스트에 실패했다.@Transactional
의 동작 방식에 있다.@Transactional
은 Spring이 해당 메서드를 감싼 프록시(proxy) 객체를 생성해 트랜잭션을 관리한다.StockService
를 필드로 가지는 클래스를 새로 만들어서 실행commit
)decreaseStock()
이 실행되어도 실제 DB에는 변경 사항이 반영되지 않은 상태 decreaseStock()
이 완료되고 실제 DB에 반영 전에 다른 스레드가decreaseStock()
을 호출하면, 다른 스레드는 갱신되기 전에 값을 가져가서 이전과 동일한 문제가 발생하게 된다.@Transactional
을 제거하고 테스트하면 테스트 성공synchronized
키워드는 같은 인스턴스에서 실행되는 여러 스레드가synchronized
는 동일한 인스턴스를 기준으로 동기화가 작동하는데,@Transactional
이 적용되면 프록시 객체가 개입하여 메서드를 실행하므로synchronized
가 원래 의도대로 작동하지 않을 가능성⭕@Transactional
을 사용하면, 스프링 AOP가 개입한다.StockService
를 직접 사용하는 것이 아닌,stockService
의 decreaseStock()
을 실행synchronized
는 프록시 객체가 아닌 원본 객체 기준으로 동작synchronized
가 제대로 동작하지 않을 수 있다.synchronized
가 원본 객체를 기준으로 동기화하려고 하지만@Transactonal
이 적용되면서 프록시 객체가 실행되므로synchronized
가 깨질 수 있다.synchronized
는 하나의 프로세스 안에서만 동기화를 보장한다.synchronized
거의 사용❌Time | Server 1 | Stock | Server 2 |
---|---|---|---|
10:00 | SELECT * FROM stock WHERE id = 1 | {id: 1, quantity: 5} | |
{id: 1, quantity: 5} | SELECT * FROM stock WHERE id = 1 | ||
10:05 | UPDATE SET quantity = 4 FROM stock WHERE id = 1 | {id: 1, quantity: 4} | |
{id: 1, quantity: 4} | UPDATE SET quantity = 4 FROM stock WHERE id = 1 |