μΈνλ°μ μ¬κ³ μμ€ν μΌλ‘ μμ보λ λμμ±μ΄μ ν΄κ²°λ°©λ²κ°μλ₯Ό λ£κ³ μ 리ν κΈμ λλ€.
κ°μλ₯Ό λ€μΌλ©° λ΄μ©κ³Ό μ½λλ githubμ μ 리ν΄λμμ΅λλ€.
μ¬κ³ μμ€ν μμ μ¬κ³ λμ κ°μμν€λ©° λμμ± λ¬Έμ κ° λ°μνλ μν©μ κ°μ νλ€.
@Entity
public class Stock {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long productId;
private Long quantity;
public Stock() {
}
public Stock(Long productId, Long quantity) {
this.productId = productId;
this.quantity = quantity;
}
public Long getQuantity() {
return quantity;
}
// μ¬κ³ κ°μ
public void decrease(Long quantity) {
if(this.quantity - quantity < 0) {
throw new RuntimeException("μ¬κ³ κ° λΆμ‘±ν©λλ€.");
}
this.quantity -= quantity;
}
}
@Service
public class StockService {
private final StockRepository stockRepository;
public StockService(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
// μ¬κ³ κ°μ
public void decreaseStock(Long id, Long quantity) {
// Stock μ‘°ν
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
stockRepository.saveAndFlush(stock);
}
}
νλμ λ°μ΄ν°λ₯Ό λ μ΄μμ threadλ sessionμ΄ μ μ΄ν λ λ°μνλ λ¬Έμ λ‘
νλμ μΈμ μ΄ λ°μ΄ν°λ₯Ό μμ μ€μΌλ, λ€λ₯Έ λ°μ΄ν°μμ μμ μ μ λ°μ΄ν°λ₯Ό μ‘°ννμ¬ λ‘μ§μ μ²λ¦¬νλ―λ‘μ¨ λ°μ΄ν°μ μ ν©μμ΄ κΉ¨μ§λ λ¬Έμ λ₯Ό λ§νλ€.
ν μ€νΈμμ μ¬μ©λλ λΌμ΄λΈλ¬λ¦¬/ν΄λμ€λ
@SpringBootTest
public class StockServiceTest {
@Autowired
private StockService stockService;
@Autowired
private StockRepository stockRepository;
@BeforeEach
public void before() {
stockRepository.saveAndFlush(new Stock(1L, 100L));
}
@AfterEach
public void after() {
stockRepository.deleteAll();
}
@Test
public void μ¬κ³ κ°μ() {
stockService.decreaseStock(1L, 1L);
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(99L, stock.getQuantity()); // 100κ° - 1κ° = 99κ°
}
@Test
public void λμμ_100κ°_μμ²() throws InterruptedException {
int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; ++i) {
executorService.submit( () -> {
try {
stockService.decreaseStock(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0L, stock.getQuantity());
}
}
μ ν
μ€νΈ μ½λμμ μ¬κ³ κ°μ()
λ μ±κ³΅μ μΌλ‘ μ€νλμ§λ§ λμμ_100κ°_μμ²()
μ μ€ν¨νλ€.
κ·Έ μ΄μ λ Race conditionμ΄ λ°μνκΈ° λλ¬Έμ΄λ€.
Race conditionμ λ μ΄μμ threadκ° κ³΅μ μμμ λμμ μ κ·Όνμ¬ λ³κ²½νλ €κ³ ν λ λ°μνλ λ¬Έμ μ΄λ€.
κΈ°λνλ μμλ
Thread1 μ¬κ³ νμΈ π Thread1 μ¬κ³ κ°μ π Thread2 μ¬κ³ νμΈ π Thread2 μ¬κ³ κ°μ π ... μ΄μ§λ§
μ€μ μ€ν μμλ
Thread1 μ¬κ³ νμΈ π Thread2 μ¬κ³ νμΈ π Thread1 μ¬κ³ κ°μ π Thread2 μ¬κ³ κ°μ π ... μ΄λ―λ‘ λ¬Έμ κ° λ°μνλ€.
race condition λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ νλμ μ€λ λ μμ μ΄ μλ£λ μ΄νμ λ€λ₯Έ μ€λ λ μμ μ νλλ‘νλ€.
κ°μμμ μκ°νλ λ°©λ²μΌλ‘λ ν¬κ² 3κ°μ§κ° μλ€.