diff --git a/app/src/main/java/org/example/BeforeOrderServiceJava.java b/app/src/main/java/org/example/BeforeOrderServiceJava.java new file mode 100644 index 0000000..cfc2fa4 --- /dev/null +++ b/app/src/main/java/org/example/BeforeOrderServiceJava.java @@ -0,0 +1,53 @@ +package org.example; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class BeforeOrderServiceJava { + + + + // 상품 DB + private final Map productDatabase = new ConcurrentHashMap<>() { + }; + // 가장 최근 주문 정보를 저장하는 DB + private final ThreadLocal> latestOrderDatabase = ThreadLocal.withInitial(ConcurrentHashMap::new); + + public BeforeOrderServiceJava() { + // 초기 상품 데이터 + productDatabase.put("apple", 100); + productDatabase.put("banana", 50); + productDatabase.put("orange", 75); + } + + // 주문 처리 메서드 + public void order(String productName, int amount) { + + productDatabase.compute(productName, (key, stock) -> { + try { + Thread.sleep(1); // 동시성 이슈 유발을 위한 인위적 지연 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + + if (stock != null && stock >= amount) { + System.out.println("Current Thread : " + Thread.currentThread().getName() + + " - CurrentStock : " + stock + " - Order : " + amount); + + latestOrderDatabase.get().put(productName, new OrderInfo(productName, amount, System.currentTimeMillis())); + + + return stock - amount; + } else { + throw new IllegalStateException("재고 부족"); + } + }); + } + + // 재고 조회 + public int getStock(String productName) { + return productDatabase.getOrDefault(productName, 0); + } +} diff --git a/app/src/main/java/org/example/OrderInfo.java b/app/src/main/java/org/example/OrderInfo.java new file mode 100644 index 0000000..05f239c --- /dev/null +++ b/app/src/main/java/org/example/OrderInfo.java @@ -0,0 +1,13 @@ +package org.example; + +public class OrderInfo { + public String productName; + public Integer amount; + public Long timestamp; + + public OrderInfo(String productName, int amount, Long timestamp) { + this.productName = productName; + this.amount = amount; + this.timestamp = timestamp; + } +} \ No newline at end of file diff --git a/app/src/test/java/org/example/BeforeOrderServiceJavaTest.java b/app/src/test/java/org/example/BeforeOrderServiceJavaTest.java new file mode 100644 index 0000000..5aa1687 --- /dev/null +++ b/app/src/test/java/org/example/BeforeOrderServiceJavaTest.java @@ -0,0 +1,53 @@ +package org.example; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class BeforeOrderServiceJavaTest { + + private final BeforeOrderServiceJava service = new BeforeOrderServiceJava(); + + @Test + void testConcurrentOrdersCauseStockMismatch() throws InterruptedException { + String productName = "apple"; + int initialStock = service.getStock(productName); + + int orderAmount = 8; + int threadCount = 100; + + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + + // 각 스레드에서 주문을 수행하는 작업 생성 + for (int i = 0; i < threadCount; i++) { + executor.execute(() -> { + try { + service.order(productName, orderAmount); + }catch (Exception e){ + System.out.println("Thread: " + Thread.currentThread().getName() + " - " + e.getMessage()); + } + finally { + latch.countDown(); // 작업 완료 후 카운트 감소 + } + }); + } + + // 모든 스레드가 작업을 완료할 때까지 대기 + latch.await(); + executor.shutdown(); + + // 최종 재고 값 확인 + int expectedStock = initialStock % orderAmount; + int actualStock = service.getStock(productName); + + System.out.println("Expected Stock: " + expectedStock + ", Actual Stock: " + actualStock); + + // 동시성 이슈로 인해 재고가 맞지 않는 경우를 확인 + assertNotEquals(expectedStock, actualStock, "재고 불일치 발생!"); + } +}