diff --git a/OrderServiceThreadSafe.kt b/OrderServiceThreadSafe.kt new file mode 100644 index 0000000..0b5880f --- /dev/null +++ b/OrderServiceThreadSafe.kt @@ -0,0 +1,66 @@ +package org.example + +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ThreadLocalRandom +import java.util.concurrent.atomic.AtomicInteger + +// 주문 정보를 저장하는 데이터 클래스 +data class OrderInfo( + val productName: String, + val amount: Int, + val timestamp: Long = System.currentTimeMillis() +) + +// 주문 처리를 담당하는 도메인 서비스 +class OrderServiceThreadSafe { + + // 상품 DB: AtomicInteger로 각 상품의 재고를 관리하여 thread-safe 보장 + private val productDatabase = ConcurrentHashMap( + mapOf( + "apple" to AtomicInteger(100), + "banana" to AtomicInteger(50), + "orange" to AtomicInteger(75) + ) + ) + + // ThreadLocal을 사용하여 각 스레드마다 독립적인 주문 데이터 보관 + private val threadLocalOrderDatabase = ThreadLocal.withInitial { + mutableMapOf>() + } + + // 주문 처리 메서드 + fun order(productName: String, amount: Int) { + val stock = productDatabase[productName] + ?: throw IllegalArgumentException("Product not found: $productName") + + // 재고 감소 (원자적 감소) + val remainingStock = stock.updateAndGet { currentStock -> + if (currentStock >= amount) { + Thread.sleep(ThreadLocalRandom.current().nextLong(5, 10)) // 지연 시간 추가 + currentStock - amount + } else { + println("Thread ${Thread.currentThread().id} - Insufficient stock for $productName") + currentStock // 재고 부족 시 변경하지 않음 + } + } + + if (remainingStock >= 0) { + // 스레드별 주문 데이터 저장 + val orderInfo = OrderInfo(productName, amount) + threadLocalOrderDatabase.get().computeIfAbsent(productName) { mutableListOf() }.add(orderInfo) + + // 로그 출력 + println("Thread ${Thread.currentThread().id} 주문 정보: $productName: 1 건 ([${amount}])") + } + } + + // 재고 조회 + fun getStock(productName: String): Int { + return productDatabase[productName]?.get() ?: 0 + } + + // 스레드별 주문 데이터 조회 + fun getThreadLocalOrderList(): Map> { + return threadLocalOrderDatabase.get() + } +} diff --git a/OrderServiceThreadSafeTest.kt b/OrderServiceThreadSafeTest.kt new file mode 100644 index 0000000..70ecb74 --- /dev/null +++ b/OrderServiceThreadSafeTest.kt @@ -0,0 +1,48 @@ +package org.example + +import org.junit.jupiter.api.Test +import java.util.concurrent.Executors +import java.util.concurrent.CountDownLatch +import kotlin.test.assertEquals + +class OrderServiceThreadSafeTest { + + private val service = OrderServiceThreadSafe() + + @Test + fun `동시 주문 테스트 - Thread-safe 검증`() { + val productName = "apple" + val orderAmount = 8 + val threadCount = 100 + val initialStock = service.getStock(productName) + + // 동시성을 위한 스레드 풀과 CountDownLatch 생성 + val executor = Executors.newFixedThreadPool(threadCount) + val latch = CountDownLatch(threadCount) + + repeat(threadCount) { threadId -> + executor.submit { + try { + // 주문 수행 + service.order(productName, orderAmount) + println("Thread $threadId 주문 정보: $productName: 1 건 ([${orderAmount}]) \n") + } finally { + latch.countDown() // 스레드 작업 완료 + } + } + } + + // 모든 스레드의 작업 완료 대기 + latch.await() + executor.shutdown() + + // 최종 재고 계산 및 검증 + val expectedStock = initialStock % orderAmount + val finalStock = service.getStock(productName) + + println("Expected Stock: $expectedStock, Final Stock: $finalStock") + + // 검증 + assertEquals(expectedStock, finalStock, "재고가 예상 값과 일치하지 않습니다.") + } +}