Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/language_java/BeforeOrderService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package language_java;

import java.util.HashMap;
import java.util.Map;

public class BeforeOrderService {

private final Map<String, Integer> productDatabase = new HashMap<>();
private final Map<String, OrderInfo> latestOrderDatabase = new HashMap<>();

public BeforeOrderService() {
productDatabase.put("apple", 100);
productDatabase.put("banana", 50);
productDatabase.put("orange", 75);
}

public void order(String productName, int amount) {
Integer currentStock = getStock(productName);

simulateDelay();

if (currentStock >= amount) {
System.out.println("Current Thread : " + Thread.currentThread().getName() +
" - CurrentStock : " + currentStock + " - Order : " + amount);
productDatabase.put(productName, currentStock - amount);
latestOrderDatabase.put(productName, new OrderInfo(productName, amount, System.currentTimeMillis()));
}
}

public Integer getStock(String productName) {
return productDatabase.getOrDefault(productName, 0);
}

private static void simulateDelay() {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
14 changes: 14 additions & 0 deletions src/language_java/OrderInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package language_java;

public class OrderInfo {
String productName;
Integer amount;
Long timestamp;

OrderInfo(String productName, Integer amount, Long timestamp) {
this.productName = productName;
this.amount = amount;
this.timestamp = timestamp;
}

}
57 changes: 57 additions & 0 deletions src/language_java/OrderService1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package language_java;

import java.util.HashMap;
import java.util.Map;

public class OrderService1 {

private final Map<String, Integer> productDatabase = new HashMap<>();
private final Map<String, OrderInfo> latestOrderDatabase = new HashMap<>();

public OrderService1() {
productDatabase.put("apple", 100);
productDatabase.put("banana", 50);
productDatabase.put("orange", 75);
}

public synchronized void orderUsingSynchronized(String productName, int amount) {
Integer currentStock = getStock(productName);

simulateDelay();

if (currentStock >= amount) {
System.out.println("Current Thread : " + Thread.currentThread().getName() +
" - CurrentStock : " + currentStock + " - Order : " + amount);
productDatabase.put(productName, currentStock - amount);
latestOrderDatabase.put(productName, new OrderInfo(productName, amount, System.currentTimeMillis()));
}
}

public void orderUsingSynchronized2(String productName, int amount) {
synchronized (this) {
Integer currentStock = getStock(productName);

simulateDelay();

if (currentStock >= amount) {
System.out.println("Current Thread : " + Thread.currentThread().getName() +
" - CurrentStock : " + currentStock + " - Order : " + amount);
productDatabase.put(productName, currentStock - amount);
latestOrderDatabase.put(productName, new OrderInfo(productName, amount, System.currentTimeMillis()));
}
}
}

public Integer getStock(String productName) {
return productDatabase.getOrDefault(productName, 0);
}

private static void simulateDelay() {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
50 changes: 50 additions & 0 deletions src/language_java/OrderService2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package language_java;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class OrderService2 {

private final Map<String, AtomicInteger> productDatabase = new HashMap<>();
private final Map<String, OrderInfo> latestOrderDatabase = new HashMap<>();

public OrderService2() {
productDatabase.put("apple", new AtomicInteger(100));
productDatabase.put("banana", new AtomicInteger(50));
productDatabase.put("orange", new AtomicInteger(75));
}

public void order(String productName, int amount) {
AtomicInteger currentStock = getStock(productName);

try {
Thread.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}

while (true) {
System.out.println("Current Thread : " + Thread.currentThread().getName() +
" - currentStock : " + currentStock + " - Order : " + amount);
int current = currentStock.get();
if (current < amount) {
return;
}

boolean success = currentStock.compareAndSet(current, current - amount);
if (success) {
System.out.println("Current Thread : " + Thread.currentThread().getName() +
" - Stock before order: " + current + " - Order: " + amount +
" - Stock after order: " + currentStock.get());
latestOrderDatabase.put(productName, new OrderInfo(productName, amount, System.currentTimeMillis()));
break;
}
}
}

public AtomicInteger getStock(String productName) {
return productDatabase.getOrDefault(productName, new AtomicInteger(0));
}
}
39 changes: 39 additions & 0 deletions src/language_java/OrderService3.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package language_java;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class OrderService3 {

private final Map<String, Integer> productDatabase = new ConcurrentHashMap<>();
private final Map<String, OrderInfo> latestOrderDatabase = new ConcurrentHashMap<>();

public OrderService3() {
productDatabase.put("apple", 100);
productDatabase.put("banana", 50);
productDatabase.put("orange", 75);
}

public void order(String productName, int amount) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}

productDatabase.compute(productName, (key, currentStock) -> {
if (currentStock == null || currentStock < amount) {
return currentStock;
}
System.out.println("Current Thread : " + Thread.currentThread().getName() +
" - CurrentStock : " + currentStock + " - Order : " + amount);
latestOrderDatabase.put(productName, new OrderInfo(productName, amount, System.currentTimeMillis()));
return currentStock - amount;
});
}

public int getStock(String productName) {
return productDatabase.getOrDefault(productName, 0);
}
}
49 changes: 49 additions & 0 deletions test/language_java/BeforeOrderServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package language_java;

import org.junit.jupiter.api.DisplayName;
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;

@DisplayName("여러 스레드가 동시에 주문을 요청할 때")
class BeforeOrderServiceTest {

private final BeforeOrderService service = new BeforeOrderService();

@Test
@DisplayName("동시성 이슈로 재고 불일치가 발생한다.")
void givenMultipleThreads_whenConcurrentOrders_thenNotEqualsExpectedStock() 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);

assertNotEquals(expectedStock, actualStock);
}
}
83 changes: 83 additions & 0 deletions test/language_java/OrderService1Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package language_java;

import org.junit.jupiter.api.DisplayName;
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.assertEquals;

@DisplayName("여러 스레드가 동시에 주문을 요청할 때")
public class OrderService1Test {

private final OrderService1 service = new OrderService1();


@Test
@DisplayName("instance 에 synchronized 키워드를 사용하면, 재고가 일치한다.")
void givenMultipleThreads_whenSynchronizedMultipleThreads_thenEqualsExpectedStock() 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.orderUsingSynchronized(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);

assertEquals(expectedStock, actualStock);
}

@Test
@DisplayName("block 에 synchronized 키워드를 사용하면, 재고가 일치한다.")
void givenMultipleThreads_whenSynchronizedBlockMultipleThreads_thenEqualsExpectedStock() 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.orderUsingSynchronized2(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);

assertEquals(expectedStock, actualStock);
}
}
49 changes: 49 additions & 0 deletions test/language_java/OrderService2Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package language_java;

import org.junit.jupiter.api.DisplayName;
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.assertEquals;

@DisplayName("여러 스레드가 동시에 주문을 요청할 때")
public class OrderService2Test {

private final OrderService2 service = new OrderService2();

@Test
@DisplayName("AtomicInteger 을 사용하면 재고가 일치한다.")
void givenMultipleThreads_whenUsingAtomicInteger_thenEqualsExpectedStock() throws InterruptedException {
String productName = "apple";
int initialStock = service.getStock(productName).get();

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).get();

System.out.println("Expected Stock : " + expectedStock + ", Actual Stock : " + actualStock);

assertEquals(expectedStock, actualStock);
}
}
Loading