From 7757cb7a0d87f09a2585f71b4cc93724aca35ca4 Mon Sep 17 00:00:00 2001 From: rmsxo200 <78577092+rmsxo200@users.noreply.github.com> Date: Thu, 2 Jan 2025 09:41:38 +0900 Subject: [PATCH] =?UTF-8?q?=EC=82=AC=EC=A0=84=EA=B3=BC=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week0/BeforeOrderService.java | 58 +++++++++++++++++++++++++++++++ week0/BeforeOrderServiceTest.java | 53 ++++++++++++++++++++++++++++ week0/OrderInfo.java | 16 +++++++++ 3 files changed, 127 insertions(+) create mode 100644 week0/BeforeOrderService.java create mode 100644 week0/BeforeOrderServiceTest.java create mode 100644 week0/OrderInfo.java diff --git a/week0/BeforeOrderService.java b/week0/BeforeOrderService.java new file mode 100644 index 0000000..e8b9b59 --- /dev/null +++ b/week0/BeforeOrderService.java @@ -0,0 +1,58 @@ +package com.hanghae.radis.service; + +import com.hanghae.radis.model.OrderInfo; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class BeforeOrderService { + // 상품 DB (공유 자원의 동시성 이슈 해결을 위해 ConcurrentMap 사용) + private final ConcurrentMap productDatabase = new ConcurrentHashMap<>(); + // 가장 최근 주문 정보를 저장하는 DB + private final Map latestOrderDatabase = new HashMap<>(); + + public BeforeOrderService() { + // 초기 상품 데이터 + productDatabase.put("apple", 100); + productDatabase.put("banana", 50); + productDatabase.put("orange", 75); + } + + // 주문 처리 메서드 + public void order(String productName, int amount) { + try { + Thread.sleep(1); // 동시성 이슈 유발을 위한 인위적 지연 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + + //스레드 안전성을 보장하기 위해 특정 키에 대한 연산을 원자적으로 수행하는 compute 사용 + productDatabase.compute(productName, (key, value) -> { + if (value >= amount) { + //주문 정보 화면 출력 + System.out.println(Thread.currentThread().getName() + " 주문정보 : \n" + + productName + ": " + amount + " 건, 현재 수량: " + value + " , 판매 후 수량: " + (value - amount)); + + //주문 성공시 최신 주문내역 저장 + latestOrderDatabase.put(productName, new OrderInfo(productName, amount, System.currentTimeMillis())); + + return value - amount; // 상품수가 주문량보다 많을 경우 업데이트 + } else { + return value; // 아닐 경우 상품수 유지 + } + }); + } + + // 재고 조회 + public int getStock(String productName) { + return productDatabase.getOrDefault(productName, 0); + } + + // 최근 주문 조회 + public OrderInfo getLatestOrder(String productName) { + return latestOrderDatabase.getOrDefault(productName, null); + } +} diff --git a/week0/BeforeOrderServiceTest.java b/week0/BeforeOrderServiceTest.java new file mode 100644 index 0000000..a36458d --- /dev/null +++ b/week0/BeforeOrderServiceTest.java @@ -0,0 +1,53 @@ +package com.hanghae.radis.service; + +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; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BeforeOrderServiceTest { + private final BeforeOrderService service = new BeforeOrderService(); + + @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); + } 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); + System.out.println("최근 주문 [" + + "상품명: " + service.getLatestOrder(productName).getProductName() + + ", 상품수: " + service.getLatestOrder(productName).getAmount() + " 건]"); + + // 재고 일치 여부 확인 + assertEquals(actualStock, expectedStock); + } +} diff --git a/week0/OrderInfo.java b/week0/OrderInfo.java new file mode 100644 index 0000000..e42bd24 --- /dev/null +++ b/week0/OrderInfo.java @@ -0,0 +1,16 @@ +package com.hanghae.radis.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class OrderInfo { + private String productName; + private Integer amount; + private Long timestamp; +}