
Race ConditionRace 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 |