From b0222f82598a504ae8b498914eeda66155556ec5 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Tue, 26 Mar 2024 18:03:16 +0900
Subject: [PATCH 01/25] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20=EC=83=88=EB=A1=9C?=
 =?UTF-8?q?=EC=9A=B4=20=EA=B8=B0=EB=8A=A5=20=E2=9C=85=20Test=20:=20?=
 =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 비동기 서비스 클래스 생성
2. 생성한 메서드에 대한 기본 테스트 작성

1. 여러 입출력 상황에 맞게 비동기 메서드 호출과 비동기 객체 병합 방법 강구
---
 .gitignore                                    |  6 +--
 .../async/service/AsyncService.java           | 21 +++++++++++
 .../thread/concurrency/AsyncServiceTest.java  | 37 +++++++++++++++++++
 3 files changed, 61 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/com/thread/concurrency/async/service/AsyncService.java
 create mode 100644 src/test/java/com/thread/concurrency/AsyncServiceTest.java

diff --git a/.gitignore b/.gitignore
index bd3712a..9cfa0cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 HELP.md
-.gradle
-build/
+.gradle/*
+build/*
 !gradle/wrapper/gradle-wrapper.jar
 !**/src/main/**/build/
 !**/src/test/**/build/
@@ -18,7 +18,7 @@ bin/
 !**/src/test/**/bin/
 
 ### IntelliJ IDEA ###
-.idea
+.idea/*
 *.iws
 *.iml
 *.ipr
diff --git a/src/main/java/com/thread/concurrency/async/service/AsyncService.java b/src/main/java/com/thread/concurrency/async/service/AsyncService.java
new file mode 100644
index 0000000..422c911
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/async/service/AsyncService.java
@@ -0,0 +1,21 @@
+package com.thread.concurrency.async.service;
+
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.CompletableFuture;
+
+@Service
+public class AsyncService {
+    @Async
+    public CompletableFuture<String> voidParamStringReturn(){
+        System.out.println("비동기적으로 실행 - "+
+            Thread.currentThread().getName());
+        try{
+            Thread.sleep(1000);
+            return CompletableFuture.completedFuture("hello world");
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/AsyncServiceTest.java b/src/test/java/com/thread/concurrency/AsyncServiceTest.java
new file mode 100644
index 0000000..65a7033
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/AsyncServiceTest.java
@@ -0,0 +1,37 @@
+package com.thread.concurrency;
+
+import com.thread.concurrency.async.service.AsyncService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+
+public class AsyncServiceTest {
+    private AsyncService asyncService;
+    @BeforeEach
+    void initUseCase() { // spring container를 사용하지 않고 순수 클래스를 사용.
+        asyncService = new AsyncService();
+    }
+    @Test
+    @DisplayName("입력은 void 출력은 String인 비동기 함수 단일 호출")
+    public void testGetString() throws ExecutionException, InterruptedException {
+        CompletableFuture<String> helloWorld = asyncService.voidParamStringReturn();
+        Assertions.assertEquals("hello world",helloWorld.get());
+    }
+
+//    @Test
+//    @DisplayName("입력은 void 출력은 String인 비동기 함수 복수 호출 그리고 결과 조합")
+//    public void testMultiGetString() throws ExecutionException, InterruptedException {
+//        List<CompletableFuture<String>> completableFutures = new ArrayList<>();
+//
+//        for (int j = 0; j <= 23; j++) {
+//            completableFutures.add(asyncService.voidParamStringReturn()));
+//        }
+//        CompletableFuture.allOf(price1,price2,price3).join();
+//        Assertions.assertEquals("hello world !!hello world !!", combin);
+//    }
+}

From fd930569f6ff7449ee39d63cd716334146344a7c Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Wed, 27 Mar 2024 09:46:14 +0900
Subject: [PATCH 02/25] =?UTF-8?q?=E2=9C=85=20Test=20:=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 메서드가 비동기 메서드인지 확인하는 테스트 작성
2. @Async를 붙인 메서드가 비동기로 동자가하지 않고 하나의 쓰레드에서 동작한다.
---
 .../async/service/AsyncService.java           |  8 +--
 .../thread/concurrency/AsyncServiceTest.java  | 66 +++++++++++++------
 2 files changed, 49 insertions(+), 25 deletions(-)

diff --git a/src/main/java/com/thread/concurrency/async/service/AsyncService.java b/src/main/java/com/thread/concurrency/async/service/AsyncService.java
index 422c911..3ed96b5 100644
--- a/src/main/java/com/thread/concurrency/async/service/AsyncService.java
+++ b/src/main/java/com/thread/concurrency/async/service/AsyncService.java
@@ -8,11 +8,11 @@
 @Service
 public class AsyncService {
     @Async
-    public CompletableFuture<String> voidParamStringReturn(){
-        System.out.println("비동기적으로 실행 - "+
-            Thread.currentThread().getName());
+    public CompletableFuture<String> voidParamStringReturn(long waitTime){
+//        System.out.println("비동기적으로 실행 - "+
+//            Thread.currentThread().getName());
         try{
-            Thread.sleep(1000);
+            Thread.sleep(waitTime);
             return CompletableFuture.completedFuture("hello world");
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
diff --git a/src/test/java/com/thread/concurrency/AsyncServiceTest.java b/src/test/java/com/thread/concurrency/AsyncServiceTest.java
index 65a7033..087e4e7 100644
--- a/src/test/java/com/thread/concurrency/AsyncServiceTest.java
+++ b/src/test/java/com/thread/concurrency/AsyncServiceTest.java
@@ -1,37 +1,61 @@
 package com.thread.concurrency;
 
 import com.thread.concurrency.async.service.AsyncService;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 
-
+@SpringBootTest
 public class AsyncServiceTest {
+    @Autowired
     private AsyncService asyncService;
-    @BeforeEach
-    void initUseCase() { // spring container를 사용하지 않고 순수 클래스를 사용.
-        asyncService = new AsyncService();
-    }
     @Test
     @DisplayName("입력은 void 출력은 String인 비동기 함수 단일 호출")
     public void testGetString() throws ExecutionException, InterruptedException {
-        CompletableFuture<String> helloWorld = asyncService.voidParamStringReturn();
+        CompletableFuture<String> helloWorld = asyncService.voidParamStringReturn(1000);
         Assertions.assertEquals("hello world",helloWorld.get());
     }
+    @Test
+    @DisplayName("입력은 void 출력은 String인 비동기 함수 단일 호출 타임아웃 발생.")
+    public void testGetStringTimeOutIsThisAsync() {
+        // voidParamStringReturn가 비동기 메서드인지 의문이 생김.
+        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
+            try {
+                return asyncService.voidParamStringReturn(4000).get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        long timeOutValue = 1;
+        TimeUnit timeUnit = TimeUnit.SECONDS;
+        // 1초가 지난 후 타임아웃 발생
+        Assertions.assertThrows(ExecutionException.class, () -> completableFuture.orTimeout(timeOutValue,timeUnit).get());
+    }
 
-//    @Test
-//    @DisplayName("입력은 void 출력은 String인 비동기 함수 복수 호출 그리고 결과 조합")
-//    public void testMultiGetString() throws ExecutionException, InterruptedException {
-//        List<CompletableFuture<String>> completableFutures = new ArrayList<>();
-//
-//        for (int j = 0; j <= 23; j++) {
-//            completableFutures.add(asyncService.voidParamStringReturn()));
-//        }
-//        CompletableFuture.allOf(price1,price2,price3).join();
-//        Assertions.assertEquals("hello world !!hello world !!", combin);
-//    }
+    @Test
+    @DisplayName("입력은 void 출력은 String인 비동기 함수 복수 호출 그리고 결과 조합")
+    public void testMultiGetString() {
+        List<CompletableFuture<String>> futures = new ArrayList<>();
+        for (int i = 1; i <= 1000; i++) { // 동기라면 10초가 걸리고 비동기라면 0.01초가 걸릴 것이다.
+            futures.add(asyncService.voidParamStringReturn(10));
+        }
+        CompletableFuture<List<String>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
+        for (CompletableFuture<String> future : futures) {
+            aggregate = aggregate.thenCompose(list -> {
+                list.add(String.valueOf(future));
+                return CompletableFuture.completedFuture(list);
+            });
+        }
+        final List<String> results = aggregate.join();
+        for (int i = 0; i < 1000; i++) {
+            System.out.println("Finished " + results.get(i));
+        }
+
+    }
 }

From 3c8ff758277d6f9ff18fd849e9b3c95e2c6e14d4 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Thu, 28 Mar 2024 00:12:26 +0900
Subject: [PATCH 03/25] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20=EC=83=88=EB=A1=9C?=
 =?UTF-8?q?=EC=9A=B4=20=EA=B8=B0=EB=8A=A5=20=E2=9C=85=20Test=20:=20?=
 =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 다중 스레드 설정을 위해 taskExecutor 메서드 빈으로 저장
2. 비동기 메서드 다중 호출 시 병렬적으로 동작하는지 testGetMultiString에서 검증
3. 비동기 메서드의 지연시간이 길어질 경우 타임아웃 발생하도록 하는 부분 검증
---
 .../SpringThreadConcurrencyApplication.java   | 17 +++++-
 .../async/controller/AsyncController.java     | 12 +++++
 .../async/service/AsyncService.java           | 15 +++---
 .../thread/concurrency/AsyncServiceTest.java  | 53 ++++++++-----------
 4 files changed, 56 insertions(+), 41 deletions(-)
 create mode 100644 src/main/java/com/thread/concurrency/async/controller/AsyncController.java

diff --git a/src/main/java/com/thread/concurrency/SpringThreadConcurrencyApplication.java b/src/main/java/com/thread/concurrency/SpringThreadConcurrencyApplication.java
index 42b9717..099710c 100644
--- a/src/main/java/com/thread/concurrency/SpringThreadConcurrencyApplication.java
+++ b/src/main/java/com/thread/concurrency/SpringThreadConcurrencyApplication.java
@@ -2,12 +2,27 @@
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
 
 @SpringBootApplication
+@EnableAsync
 public class SpringThreadConcurrencyApplication {
 
     public static void main(String[] args) {
         SpringApplication.run(SpringThreadConcurrencyApplication.class, args);
     }
-
+    @Bean
+    public Executor taskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // Spring에서 사용하는 스레드를 제어한느 설정
+        executor.setCorePoolSize(50); // thread-pool에 살아있는 thread의 최소 개수
+        executor.setMaxPoolSize(50);  // thread-pool에서 사용할 수 있는 최대 개수
+        executor.setQueueCapacity(500); //thread-pool에 최대 queue 크기
+        executor.setThreadNamePrefix("AsyncApp-");
+        executor.initialize();
+        return executor;
+    }
 }
diff --git a/src/main/java/com/thread/concurrency/async/controller/AsyncController.java b/src/main/java/com/thread/concurrency/async/controller/AsyncController.java
new file mode 100644
index 0000000..64037ed
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/async/controller/AsyncController.java
@@ -0,0 +1,12 @@
+package com.thread.concurrency.async.controller;
+
+import com.thread.concurrency.async.service.AsyncService;
+import org.springframework.stereotype.Controller;
+@Controller
+public class AsyncController {
+    private final AsyncService asyncService;
+
+    public AsyncController(AsyncService asyncService) {
+        this.asyncService = asyncService;
+    }
+}
diff --git a/src/main/java/com/thread/concurrency/async/service/AsyncService.java b/src/main/java/com/thread/concurrency/async/service/AsyncService.java
index 3ed96b5..9cb2a00 100644
--- a/src/main/java/com/thread/concurrency/async/service/AsyncService.java
+++ b/src/main/java/com/thread/concurrency/async/service/AsyncService.java
@@ -5,17 +5,14 @@
 
 import java.util.concurrent.CompletableFuture;
 
+
 @Service
 public class AsyncService {
     @Async
-    public CompletableFuture<String> voidParamStringReturn(long waitTime){
-//        System.out.println("비동기적으로 실행 - "+
-//            Thread.currentThread().getName());
-        try{
-            Thread.sleep(waitTime);
-            return CompletableFuture.completedFuture("hello world");
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
+    public CompletableFuture<String> voidParamStringReturn (long waitTime, String message) throws InterruptedException{
+        System.out.println("비동기적으로 실행 - "+
+            Thread.currentThread().getName());
+        Thread.sleep(waitTime);
+        return CompletableFuture.completedFuture(message);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/AsyncServiceTest.java b/src/test/java/com/thread/concurrency/AsyncServiceTest.java
index 087e4e7..90b0d2d 100644
--- a/src/test/java/com/thread/concurrency/AsyncServiceTest.java
+++ b/src/test/java/com/thread/concurrency/AsyncServiceTest.java
@@ -2,6 +2,8 @@
 
 import com.thread.concurrency.async.service.AsyncService;
 import org.junit.jupiter.api.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
@@ -13,49 +15,38 @@
 
 @SpringBootTest
 public class AsyncServiceTest {
+    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceTest.class);
     @Autowired
     private AsyncService asyncService;
+
     @Test
     @DisplayName("입력은 void 출력은 String인 비동기 함수 단일 호출")
     public void testGetString() throws ExecutionException, InterruptedException {
-        CompletableFuture<String> helloWorld = asyncService.voidParamStringReturn(1000);
-        Assertions.assertEquals("hello world",helloWorld.get());
+        CompletableFuture<String> helloWorld = asyncService.voidParamStringReturn(1000,  "기본 메세지");
+        Assertions.assertEquals("기본 메세지",helloWorld.get());
     }
+
+    @Test
+    @DisplayName("입력은 void 출력은 String인 비동기 함수 다중 호출")
+    public void testGetMultiString() throws InterruptedException {
+        List<CompletableFuture<String>> hellos = new ArrayList<>();
+        for(int i=0; i<100; i++){
+            hellos.add(asyncService.voidParamStringReturn(1000,i+"번째 메세지"));
+        }
+        // 모든 비동기 호출이 완료될 때까지 대기하고 결과를 리스트에 넣기
+        List<String> results = hellos.stream().map(CompletableFuture::join)
+                .toList();
+        results.forEach(logger::info);
+    }
+
     @Test
     @DisplayName("입력은 void 출력은 String인 비동기 함수 단일 호출 타임아웃 발생.")
-    public void testGetStringTimeOutIsThisAsync() {
+    public void testGetStringTimeOutIsThisAsync() throws InterruptedException {
         // voidParamStringReturn가 비동기 메서드인지 의문이 생김.
-        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
-            try {
-                return asyncService.voidParamStringReturn(4000).get();
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        });
+        CompletableFuture<String> completableFuture = asyncService.voidParamStringReturn(4000, "타임아웃 발생 안 함!");
         long timeOutValue = 1;
         TimeUnit timeUnit = TimeUnit.SECONDS;
         // 1초가 지난 후 타임아웃 발생
         Assertions.assertThrows(ExecutionException.class, () -> completableFuture.orTimeout(timeOutValue,timeUnit).get());
     }
-
-    @Test
-    @DisplayName("입력은 void 출력은 String인 비동기 함수 복수 호출 그리고 결과 조합")
-    public void testMultiGetString() {
-        List<CompletableFuture<String>> futures = new ArrayList<>();
-        for (int i = 1; i <= 1000; i++) { // 동기라면 10초가 걸리고 비동기라면 0.01초가 걸릴 것이다.
-            futures.add(asyncService.voidParamStringReturn(10));
-        }
-        CompletableFuture<List<String>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
-        for (CompletableFuture<String> future : futures) {
-            aggregate = aggregate.thenCompose(list -> {
-                list.add(String.valueOf(future));
-                return CompletableFuture.completedFuture(list);
-            });
-        }
-        final List<String> results = aggregate.join();
-        for (int i = 0; i < 1000; i++) {
-            System.out.println("Finished " + results.get(i));
-        }
-
-    }
 }

From c734c167888058b7b6275052cce642b79896531b Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Thu, 28 Mar 2024 12:32:08 +0900
Subject: [PATCH 04/25] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20=EC=83=88=EB=A1=9C?=
 =?UTF-8?q?=EC=9A=B4=20=EA=B8=B0=EB=8A=A5=20=E2=9C=85=20Test=20:=20?=
 =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 비동기 호출을 10회 하는 비동기 메서드를 10회 호출하는 calculateRunTime 작성
2. 비동기 호출이 100회 일 때 50개는 쓰레드가 실행하고 나머지 50개는 블록킹 큐에서 대기한다.
---
 .../async/controller/AsyncController.java     | 23 +++++++++++++
 .../concurrency/AsyncControllerTest.java      | 32 +++++++++++++++++++
 2 files changed, 55 insertions(+)
 create mode 100644 src/test/java/com/thread/concurrency/AsyncControllerTest.java

diff --git a/src/main/java/com/thread/concurrency/async/controller/AsyncController.java b/src/main/java/com/thread/concurrency/async/controller/AsyncController.java
index 64037ed..6be4cdf 100644
--- a/src/main/java/com/thread/concurrency/async/controller/AsyncController.java
+++ b/src/main/java/com/thread/concurrency/async/controller/AsyncController.java
@@ -1,7 +1,15 @@
 package com.thread.concurrency.async.controller;
 
 import com.thread.concurrency.async.service.AsyncService;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Controller;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
 @Controller
 public class AsyncController {
     private final AsyncService asyncService;
@@ -9,4 +17,19 @@ public class AsyncController {
     public AsyncController(AsyncService asyncService) {
         this.asyncService = asyncService;
     }
+
+    @Async
+    public CompletableFuture<String> calculateRunTime(int cnt, int waitTime) throws InterruptedException {
+        LocalTime lt1 = LocalTime.now();
+        List<CompletableFuture<String>> hellos = new ArrayList<>();
+        for(int i=0; i<cnt; i++){
+            hellos.add(asyncService.voidParamStringReturn(waitTime,i+"번째 메세지"));
+        }
+        // 모든 비동기 호출이 완료될 때까지 대기하고 결과를 리스트에 넣기
+        List<String> results = hellos.stream().map(CompletableFuture::join)
+            .toList();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).toMillis();
+        return CompletableFuture.completedFuture(dif+"가 걸렸습니다.");
+    }
 }
diff --git a/src/test/java/com/thread/concurrency/AsyncControllerTest.java b/src/test/java/com/thread/concurrency/AsyncControllerTest.java
new file mode 100644
index 0000000..59d0f06
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/AsyncControllerTest.java
@@ -0,0 +1,32 @@
+package com.thread.concurrency;
+
+import com.thread.concurrency.async.controller.AsyncController;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+@SpringBootTest
+public class AsyncControllerTest {
+    private static final Logger logger = LoggerFactory.getLogger(AsyncServiceTest.class);
+
+    @Autowired
+    private AsyncController asyncController;
+
+    @Test
+    public void invokeMultiAsyncMethod() throws InterruptedException {
+        List<CompletableFuture<String>> hellos = new ArrayList<>();
+        for(int i=0; i<10; i++){
+            hellos.add(asyncController.calculateRunTime(10, 1000));
+        }
+        // 모든 비동기 호출이 완료될 때까지 대기하고 결과를 리스트에 넣기
+        List<String> results = hellos.stream().map(CompletableFuture::join)
+            .toList();
+        results.forEach(logger::info);
+    }
+}

From f78aeaca360dd79080ff9f7ba8fd0db7f42d29f4 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sat, 30 Mar 2024 16:01:54 +0900
Subject: [PATCH 05/25] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20=EC=83=88=EB=A1=9C?=
 =?UTF-8?q?=EC=9A=B4=20=EA=B8=B0=EB=8A=A5=20=E2=9C=85=20Test=20:=20?=
 =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. BasicCounter 구현. 멀티 쓰레드로 카운터 동시 업데이트 불가 테스트
2. SynchronizedCounter 구현. 멀티 쓰레드로 카운터 동시 업데이트 가능 테스트. Synchronized만 사용했을 때는 0.5s 소요
3. Completable 사용하면 실행시간이 대폭 감소
---
 build.gradle.kts                              |  1 +
 .../concurrency/counter/BasicCounter.java     | 18 +++++
 .../thread/concurrency/counter/Counter.java   |  6 ++
 .../counter/SynchronizedCounter.java          | 21 +++++
 .../concurrency/counter/BasicCounterTest.java | 65 ++++++++++++++++
 .../counter/SynchronizedCounterTest.java      | 76 +++++++++++++++++++
 .../com/thread/concurrency/package-info.java  |  1 +
 7 files changed, 188 insertions(+)
 create mode 100644 src/main/java/com/thread/concurrency/counter/BasicCounter.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/Counter.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/SynchronizedCounter.java
 create mode 100644 src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
 create mode 100644 src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
 create mode 100644 src/test/java/com/thread/concurrency/package-info.java

diff --git a/build.gradle.kts b/build.gradle.kts
index dfe6a2a..85502fe 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,6 +18,7 @@ repositories {
 
 dependencies {
     implementation("org.springframework.boot:spring-boot-starter")
+    implementation("net.jcip:jcip-annotations:1.0")
     testImplementation("org.springframework.boot:spring-boot-starter-test")
     testRuntimeOnly("org.junit.platform:junit-platform-launcher")
     testRuntimeOnly("org.reactivestreams:reactive-streams")
diff --git a/src/main/java/com/thread/concurrency/counter/BasicCounter.java b/src/main/java/com/thread/concurrency/counter/BasicCounter.java
new file mode 100644
index 0000000..ae72fd6
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/BasicCounter.java
@@ -0,0 +1,18 @@
+package com.thread.concurrency.counter;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class BasicCounter implements Counter{
+    private static int count = 100;
+
+    @Override
+    public void add(int value) {
+        count += value;
+    }
+
+    @Override
+    public int show() {
+        return count;
+    }
+}
diff --git a/src/main/java/com/thread/concurrency/counter/Counter.java b/src/main/java/com/thread/concurrency/counter/Counter.java
new file mode 100644
index 0000000..4575c3d
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/Counter.java
@@ -0,0 +1,6 @@
+package com.thread.concurrency.counter;
+
+public interface Counter {
+    void add(int value);
+    int show();
+}
diff --git a/src/main/java/com/thread/concurrency/counter/SynchronizedCounter.java b/src/main/java/com/thread/concurrency/counter/SynchronizedCounter.java
new file mode 100644
index 0000000..be6863c
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/SynchronizedCounter.java
@@ -0,0 +1,21 @@
+package com.thread.concurrency.counter;
+
+import net.jcip.annotations.GuardedBy;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SynchronizedCounter implements Counter{
+
+    @GuardedBy("this")
+    private int counter = 100;
+
+    @Override
+    public synchronized void add(int value) {
+        counter += value;
+    }
+
+    @Override
+    public synchronized int show() {
+        return counter;
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java b/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
new file mode 100644
index 0000000..7a8a2e3
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
@@ -0,0 +1,65 @@
+package com.thread.concurrency.counter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+@SpringBootTest
+public class BasicCounterTest {
+
+    private final BasicCounter basicCounter;
+    private final int counteNumber = 1;
+    private final int totalCount = 100;
+
+    @Autowired
+    public BasicCounterTest(BasicCounter basicCounter) {
+        this.basicCounter = basicCounter;
+    }
+
+    @Test
+    @DisplayName("스레드 안전하지 않는 카운터로 동시에 여러 더하기 수행하기. 실패 예상")
+    public void 여러_더하기_수행(){
+        int initalCount = basicCounter.show();
+
+       for(int i=0; i<totalCount; i++){
+           CompletableFuture.runAsync(() -> {
+               basicCounter.add(counteNumber);
+           });
+       }
+        int finalCount = basicCounter.show();
+        Assertions.assertNotEquals(initalCount+totalCount*counteNumber, finalCount);
+    }
+
+    @Test
+    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
+    public void 여러_더하기_수행_CompletableFuture() {
+        int initalCount = basicCounter.show();
+        List<CompletableFuture<Void>> tasks = new ArrayList<>();
+        for(int i=0; i<totalCount; i++){
+            tasks.add(CompletableFuture.runAsync(() -> basicCounter.add(counteNumber)));
+        }
+
+        CompletableFuture<List<Void>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
+        for (CompletableFuture<Void> future : tasks) {
+            aggregate = aggregate.thenCompose(list -> {
+                try {
+                    list.add(future.get());
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+                return CompletableFuture.completedFuture(list);
+            });
+        }
+        aggregate.join(); // 전체 비동기 결과 집계
+        int finalCount = basicCounter.show();
+        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
new file mode 100644
index 0000000..340112d
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
@@ -0,0 +1,76 @@
+package com.thread.concurrency.counter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.*;
+
+@SpringBootTest
+public class SynchronizedCounterTest {
+
+    private final SynchronizedCounter counter;
+    private final int counteNumber = 1;
+    private final int totalCount = 100;
+
+    @Autowired
+    public SynchronizedCounterTest(SynchronizedCounter counter) {
+        this.counter = counter;
+    }
+
+    /**
+     * 실행 완료까지 0.5s 정도 소요
+     * @throws InterruptedException
+     */
+    @Test
+    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
+    public void 여러_더하기_수행_Executor() throws InterruptedException {
+        int initalCount = counter.show();
+        int numberOfThreads = totalCount;
+        ExecutorService service = Executors.newFixedThreadPool(10);
+        CountDownLatch latch = new CountDownLatch(numberOfThreads);
+        for (int i = 0; i < numberOfThreads; i++) {
+            service.submit(() -> {
+                counter.add(counteNumber);
+                latch.countDown();
+            });
+        }
+        latch.await();
+        int finalCount = counter.show();
+
+        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
+    }
+
+    /**
+     * 실행 완료까지 0.002s 소요
+     */
+    @Test
+    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
+    public void 여러_더하기_수행_CompletableFuture() {
+        int initalCount = counter.show();
+        List<CompletableFuture<Void>> tasks = new ArrayList<>();
+        for(int i=0; i<totalCount; i++){
+            tasks.add(CompletableFuture.runAsync(() -> counter.add(counteNumber)));
+        }
+
+        CompletableFuture<List<Void>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
+        for (CompletableFuture<Void> future : tasks) {
+            aggregate = aggregate.thenCompose(list -> {
+                try {
+                    list.add(future.get());
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+                return CompletableFuture.completedFuture(list);
+            });
+        }
+        aggregate.join(); // 전체 비동기 결과 집계
+        int finalCount = counter.show();
+        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/package-info.java b/src/test/java/com/thread/concurrency/package-info.java
new file mode 100644
index 0000000..ae29573
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/package-info.java
@@ -0,0 +1 @@
+package com.thread.concurrency;

From b96f482e8005c3abff80acb8906e4936e4e1cb79 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sat, 30 Mar 2024 16:30:32 +0900
Subject: [PATCH 06/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. SynchronizedCounter 테스트의 실질적 실행시간을 로깅하도록 리팩토링
---
 .../concurrency/counter/BasicCounterTest.java | 30 ----------------
 .../counter/SynchronizedCounterTest.java      | 36 ++++++++++++-------
 2 files changed, 23 insertions(+), 43 deletions(-)

diff --git a/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java b/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
index 7a8a2e3..ffc7e7f 100644
--- a/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
@@ -1,16 +1,11 @@
 package com.thread.concurrency.counter;
 
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
 
 @SpringBootTest
 public class BasicCounterTest {
@@ -37,29 +32,4 @@ public BasicCounterTest(BasicCounter basicCounter) {
         int finalCount = basicCounter.show();
         Assertions.assertNotEquals(initalCount+totalCount*counteNumber, finalCount);
     }
-
-    @Test
-    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
-    public void 여러_더하기_수행_CompletableFuture() {
-        int initalCount = basicCounter.show();
-        List<CompletableFuture<Void>> tasks = new ArrayList<>();
-        for(int i=0; i<totalCount; i++){
-            tasks.add(CompletableFuture.runAsync(() -> basicCounter.add(counteNumber)));
-        }
-
-        CompletableFuture<List<Void>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
-        for (CompletableFuture<Void> future : tasks) {
-            aggregate = aggregate.thenCompose(list -> {
-                try {
-                    list.add(future.get());
-                } catch (InterruptedException | ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-                return CompletableFuture.completedFuture(list);
-            });
-        }
-        aggregate.join(); // 전체 비동기 결과 집계
-        int finalCount = basicCounter.show();
-        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
-    }
 }
diff --git a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
index 340112d..8d55ba7 100644
--- a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
@@ -1,11 +1,16 @@
 package com.thread.concurrency.counter;
 
+import com.thread.concurrency.AsyncServiceTest;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
+import java.time.Duration;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -14,27 +19,24 @@
 @SpringBootTest
 public class SynchronizedCounterTest {
 
-    private final SynchronizedCounter counter;
     private final int counteNumber = 1;
     private final int totalCount = 100;
-
-    @Autowired
-    public SynchronizedCounterTest(SynchronizedCounter counter) {
-        this.counter = counter;
-    }
+    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
 
     /**
-     * 실행 완료까지 0.5s 정도 소요
+     * 실행 완료까지 871ms 정도 소요
      * @throws InterruptedException
      */
     @Test
     @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
     public void 여러_더하기_수행_Executor() throws InterruptedException {
+        SynchronizedCounter counter = new SynchronizedCounter();
+        LocalTime lt1 = LocalTime.now();
         int initalCount = counter.show();
-        int numberOfThreads = totalCount;
-        ExecutorService service = Executors.newFixedThreadPool(10);
-        CountDownLatch latch = new CountDownLatch(numberOfThreads);
-        for (int i = 0; i < numberOfThreads; i++) {
+        int numberOfThreads = 15;
+        ExecutorService service = Executors.newFixedThreadPool(15);
+        CountDownLatch latch = new CountDownLatch(100);
+        for (int i = 0; i < totalCount; i++) {
             service.submit(() -> {
                 counter.add(counteNumber);
                 latch.countDown();
@@ -42,16 +44,20 @@ public SynchronizedCounterTest(SynchronizedCounter counter) {
         }
         latch.await();
         int finalCount = counter.show();
-
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : "+dif/1000+"ms");
         Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
     }
 
     /**
-     * 실행 완료까지 0.002s 소요
+     * 실행 완료까지 1061ms 소요
      */
     @Test
     @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
     public void 여러_더하기_수행_CompletableFuture() {
+        SynchronizedCounter counter = new SynchronizedCounter();
+        LocalTime lt1 = LocalTime.now();
         int initalCount = counter.show();
         List<CompletableFuture<Void>> tasks = new ArrayList<>();
         for(int i=0; i<totalCount; i++){
@@ -71,6 +77,10 @@ public SynchronizedCounterTest(SynchronizedCounter counter) {
         }
         aggregate.join(); // 전체 비동기 결과 집계
         int finalCount = counter.show();
+
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        logger.info("여러_더하기_수행_CompletableFuture 테스트가 걸린 시간 : "+dif/1000+"ms");
         Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
     }
 }

From f52855581a054825dda786de9578818b91989d62 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sat, 30 Mar 2024 17:52:12 +0900
Subject: [PATCH 07/25] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20=EC=83=88=EB=A1=9C?=
 =?UTF-8?q?=EC=9A=B4=20=EA=B8=B0=EB=8A=A5=20=E2=9C=85=20Test=20:=20?=
 =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. CompletableFuture를 이용한 카운터 작성
2. 카운터에 int가 아닌 CompletableFuture<Integer>를 저장
3. 작업의 완성본을 저장하지 않고 작업 진행 중인 Future 객체를 저장하는 점에서 추후 캐싱 시나리오에 도움될 것으로 보임
---
 .../counter/CompletableFutureCounter.java     | 30 +++++++++++++
 .../counter/CompletableFutureCounterTest.java | 43 +++++++++++++++++++
 .../counter/SynchronizedCounterTest.java      |  4 +-
 3 files changed, 75 insertions(+), 2 deletions(-)
 create mode 100644 src/main/java/com/thread/concurrency/counter/CompletableFutureCounter.java
 create mode 100644 src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java

diff --git a/src/main/java/com/thread/concurrency/counter/CompletableFutureCounter.java b/src/main/java/com/thread/concurrency/counter/CompletableFutureCounter.java
new file mode 100644
index 0000000..0a32d60
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/CompletableFutureCounter.java
@@ -0,0 +1,30 @@
+package com.thread.concurrency.counter;
+
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class CompletableFutureCounter implements Counter{
+
+    private CompletableFuture<Integer> counter = new CompletableFuture<>();
+
+    @Override
+    public void add(int value) {
+        // 연산이 진행 중이라면 기다렸다가 thenApply
+        // 카운트에 값 저장
+        counter = counter.thenApply((c) -> c + value);
+    }
+
+    @Override
+    public int show() {
+        try {
+            // 카운트에 대한 연산이 실행 중이라면 기다렸다가 가져오기
+            return counter.get();
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java b/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
new file mode 100644
index 0000000..236bfd9
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
@@ -0,0 +1,43 @@
+package com.thread.concurrency.counter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@SpringBootTest
+public class CompletableFutureCounterTest {
+    private final int counteNumber = 1;
+    private final int totalCount = 10000;
+    private static final Logger logger = LoggerFactory.getLogger(CompletableFutureCounterTest.class);
+
+    @Test
+    public void 여러_더하기_수행_Compltable() throws InterruptedException {
+        SynchronizedCounter counter = new SynchronizedCounter();
+        LocalTime lt1 = LocalTime.now();
+
+        int initalCount = counter.show();
+        ExecutorService service = Executors.newFixedThreadPool(15);
+        CountDownLatch latch = new CountDownLatch(totalCount);
+        for (int i = 0; i < totalCount; i++) {
+            service.submit(() -> {
+                counter.add(counteNumber);
+                latch.countDown();
+            });
+        }
+        latch.await();
+        int finalCount = counter.show();
+
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        logger.info("여러_더하기_수행_Compltable 테스트가 걸린 시간 : "+dif/1000+"ms");
+        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
index 8d55ba7..0c24bec 100644
--- a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
@@ -20,7 +20,7 @@
 public class SynchronizedCounterTest {
 
     private final int counteNumber = 1;
-    private final int totalCount = 100;
+    private final int totalCount = 10000;
     private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
 
     /**
@@ -35,7 +35,7 @@ public class SynchronizedCounterTest {
         int initalCount = counter.show();
         int numberOfThreads = 15;
         ExecutorService service = Executors.newFixedThreadPool(15);
-        CountDownLatch latch = new CountDownLatch(100);
+        CountDownLatch latch = new CountDownLatch(totalCount);
         for (int i = 0; i < totalCount; i++) {
             service.submit(() -> {
                 counter.add(counteNumber);

From 83c6f93bbe816d26ac159c8064c4d50cac885f5e Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Tue, 2 Apr 2024 15:55:55 +0900
Subject: [PATCH 08/25] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20=EC=83=88=EB=A1=9C?=
 =?UTF-8?q?=EC=9A=B4=20=EA=B8=B0=EB=8A=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 컨슈머는 브로커에게서 이벤트를 가져와 처리한다.
2. 프로듀서는 브로커에게 이벤트를 저장한다.
3. 브로커는 큐를 관리하고 큐에 이벤트 삽입과 제거를 담당한다.
---
 .../producerCustomer/CounterBroker.java       | 28 +++++++++++++++++++
 .../producerCustomer/CounterCustomer.java     | 16 +++++++++++
 .../producerCustomer/CounterProducer.java     | 13 +++++++++
 .../concurrency/counter/QueueCounter.java     | 18 ++++++++++++
 4 files changed, 75 insertions(+)
 create mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
 create mode 100644 src/test/java/com/thread/concurrency/counter/QueueCounter.java

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
new file mode 100644
index 0000000..e759333
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
@@ -0,0 +1,28 @@
+package com.thread.concurrency.counter.producerCustomer;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.function.Function;
+
+public class CounterBroker {
+    private BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>();
+    private Integer count;
+    public void addEvent(int value){
+        try{
+            queue.put((c) -> c + value); // 이 이벤트를 컨슈머가 처리할 당시 count와 value를 더한 값을 출력한다.
+        }
+        catch(InterruptedException e){
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    public void consumEvent(){
+        try{
+            // "value를 더한다"라는 이벤트는 현재 스레드만 가질 수 있다.
+            count = queue.take().apply(count);
+        }
+        catch(InterruptedException e){
+            Thread.currentThread().interrupt();
+        }
+    }
+}
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
new file mode 100644
index 0000000..b539b2c
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
@@ -0,0 +1,16 @@
+package com.thread.concurrency.counter.producerCustomer;
+
+
+public class CounterCustomer{
+    private CounterBroker counterBroker;
+
+    public CounterCustomer(CounterBroker counterBroker) {
+        this.counterBroker = counterBroker;
+    }
+
+    public void consumEvent(){
+        while(true){
+            counterBroker.consumEvent();
+        }
+    }
+}
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
new file mode 100644
index 0000000..ac94c74
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
@@ -0,0 +1,13 @@
+package com.thread.concurrency.counter.producerCustomer;
+
+public class CounterProducer {
+    private CounterBroker counterBroker;
+
+    public CounterProducer(CounterBroker counterBroker) {
+        this.counterBroker = counterBroker;
+    }
+
+    public void add(int value){
+        counterBroker.addEvent(value);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounter.java b/src/test/java/com/thread/concurrency/counter/QueueCounter.java
new file mode 100644
index 0000000..a45cea7
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/counter/QueueCounter.java
@@ -0,0 +1,18 @@
+package com.thread.concurrency.counter;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QueueCounter {
+    private final int counteNumber = 1;
+    private final int totalCount = 10000;
+    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
+
+    @Test
+    @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
+    public void 프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머(){
+
+    }
+}

From 0b332226916f7cb13869fee3696f3dcc31c07cac Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Tue, 2 Apr 2024 17:20:39 +0900
Subject: [PATCH 09/25] =?UTF-8?q?=E2=9C=85=20Test=20:=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 프로듀서-컨슈머 패턴을 이용한 카운터 구현을 위해 테스트를 작성
2. 시간 측정 결과 기존 CompletableFutureCounter에 2배 시간이 걸리는 걸로 나왔다.

To Do
CompletableFutureCounterTest와 QueueCounterTest에 쓰이는 쓰레드 수를 같게해서 다시 시간을 측정해보기
---
 .../producerCustomer/CounterBroker.java       | 22 ++++++--
 .../producerCustomer/CounterCustomer.java     | 12 ++--
 .../producerCustomer/CounterProducer.java     |  2 +-
 .../concurrency/counter/QueueCounter.java     | 18 ------
 .../concurrency/counter/QueueCounterTest.java | 56 +++++++++++++++++++
 5 files changed, 83 insertions(+), 27 deletions(-)
 delete mode 100644 src/test/java/com/thread/concurrency/counter/QueueCounter.java
 create mode 100644 src/test/java/com/thread/concurrency/counter/QueueCounterTest.java

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
index e759333..c6a1e8c 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
@@ -2,14 +2,21 @@
 
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
+import java.util.function.IntUnaryOperator;
+import java.util.function.UnaryOperator;
 
 public class CounterBroker {
-    private BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>();
-    private Integer count;
+    // 100개의 이벤트를 저장할 수 있다.
+    private BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(100);
+    private AtomicInteger count = new AtomicInteger(100); // AtomicInteger로 변경
     public void addEvent(int value){
         try{
-            queue.put((c) -> c + value); // 이 이벤트를 컨슈머가 처리할 당시 count와 value를 더한 값을 출력한다.
+            // 이 이벤트를 컨슈머가 처리할 당시 count와 value를 더한 값을 출력한다.
+            // 만약 4초 동안 프로듀서가 요소를 넣지 못하면 timeout이 발생한다.
+            queue.offer((c) -> c + value, 4, TimeUnit.SECONDS);
         }
         catch(InterruptedException e){
             Thread.currentThread().interrupt();
@@ -19,10 +26,17 @@ public void addEvent(int value){
     public void consumEvent(){
         try{
             // "value를 더한다"라는 이벤트는 현재 스레드만 가질 수 있다.
-            count = queue.take().apply(count);
+            // AtomicInteger의 updateAndGet 메서드를 사용하여 원자적으로 값을 업데이트
+            Function<Integer, Integer> event = queue.take();
+            IntUnaryOperator operator = event::apply;
+            count.updateAndGet(operator);
         }
         catch(InterruptedException e){
             Thread.currentThread().interrupt();
         }
     }
+
+    public int show(){
+        return count.get(); // AtomicInteger의 get 메서드를 사용하여 값을 가져옴
+    }
 }
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
index b539b2c..ae14d8b 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
@@ -1,15 +1,19 @@
 package com.thread.concurrency.counter.producerCustomer;
 
 
-public class CounterCustomer{
-    private CounterBroker counterBroker;
+public class CounterCustomer {
+    private final CounterBroker counterBroker;
+    private volatile boolean running;
 
     public CounterCustomer(CounterBroker counterBroker) {
         this.counterBroker = counterBroker;
+        this.running = true;
+    }
+    public void stop() {
+        running = false; // 스레드 종료를 위해 running 플래그를 false로 설정
     }
-
     public void consumEvent(){
-        while(true){
+        while(running){
             counterBroker.consumEvent();
         }
     }
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
index ac94c74..c35110d 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
@@ -1,7 +1,7 @@
 package com.thread.concurrency.counter.producerCustomer;
 
 public class CounterProducer {
-    private CounterBroker counterBroker;
+    private final CounterBroker counterBroker;
 
     public CounterProducer(CounterBroker counterBroker) {
         this.counterBroker = counterBroker;
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounter.java b/src/test/java/com/thread/concurrency/counter/QueueCounter.java
deleted file mode 100644
index a45cea7..0000000
--- a/src/test/java/com/thread/concurrency/counter/QueueCounter.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.thread.concurrency.counter;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class QueueCounter {
-    private final int counteNumber = 1;
-    private final int totalCount = 10000;
-    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
-
-    @Test
-    @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
-    public void 프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머(){
-
-    }
-}
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
new file mode 100644
index 0000000..595bac8
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
@@ -0,0 +1,56 @@
+package com.thread.concurrency.counter;
+
+import com.thread.concurrency.counter.producerCustomer.CounterBroker;
+import com.thread.concurrency.counter.producerCustomer.CounterCustomer;
+import com.thread.concurrency.counter.producerCustomer.CounterProducer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class QueueCounterTest {
+    private final int counteNumber = 1;
+    private final int totalCount = 10000;
+    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
+    @Test
+    @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
+    public void 프로듀서_컨슈며_더하기_멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
+        CounterBroker counterBroker = new CounterBroker();
+        CounterCustomer customer = new CounterCustomer(counterBroker);
+        CounterProducer producer = new CounterProducer(counterBroker);
+        LocalTime lt1 = LocalTime.now();
+        int initalCount = counterBroker.show();
+        ExecutorService service = Executors.newFixedThreadPool(15);
+        CountDownLatch latch = new CountDownLatch(totalCount);
+
+        // CounterCustomer 스레드 생성 및 비동기로 처리 시작
+        List<CompletableFuture<Void>> futureList = new ArrayList<>();
+        for(int i=0; i<3; i++){
+            futureList.add(CompletableFuture.runAsync(customer::consumEvent));
+        }
+        // 프로듀서 스레드 생성
+        for (int i = 0; i < totalCount; i++) {
+            service.submit(() -> {
+                producer.add(counteNumber);
+                latch.countDown();
+            });
+        }
+        latch.await();
+
+        int finalCount = counterBroker.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000 + "ms");
+        Assertions.assertEquals(initalCount + totalCount*counteNumber, finalCount);
+    }
+}

From 06cbe1f570de27d0e5ac5725863b38b4ee0b8b50 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Wed, 3 Apr 2024 08:56:30 +0900
Subject: [PATCH 10/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 브로커를 테스트에서 제거
2. 단일한 큐를 컨슈머와 프로듀서가 공유
3. 컨슈머가 카운터를 업데이트하는 역할을 가짐

To Do
컨슈머의 consumEvent를 실행하는 스레드와 show를 호출하는 메인 스레드 간 싱크가 맞지 않는다.
모든 consumEvent가 끝나고 show를 호출할 수 있는 방법이 필요하다.
---
 .../producerCustomer/CounterBroker.java       | 50 +++++++------------
 .../producerCustomer/CounterCustomer.java     | 32 ++++++++----
 .../producerCustomer/CounterProducer.java     | 13 +++--
 .../concurrency/counter/QueueCounterTest.java | 39 ++++++++-------
 4 files changed, 70 insertions(+), 64 deletions(-)

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
index c6a1e8c..da1368a 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
@@ -3,40 +3,26 @@
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
-import java.util.function.IntUnaryOperator;
-import java.util.function.UnaryOperator;
 
 public class CounterBroker {
-    // 100개의 이벤트를 저장할 수 있다.
-    private BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(100);
-    private AtomicInteger count = new AtomicInteger(100); // AtomicInteger로 변경
-    public void addEvent(int value){
-        try{
-            // 이 이벤트를 컨슈머가 처리할 당시 count와 value를 더한 값을 출력한다.
-            // 만약 4초 동안 프로듀서가 요소를 넣지 못하면 timeout이 발생한다.
-            queue.offer((c) -> c + value, 4, TimeUnit.SECONDS);
-        }
-        catch(InterruptedException e){
-            Thread.currentThread().interrupt();
-        }
-    }
+    // 최대 1000개의 이벤트를 저장할 수 있다.
+    private final BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(1000);
 
-    public void consumEvent(){
-        try{
-            // "value를 더한다"라는 이벤트는 현재 스레드만 가질 수 있다.
-            // AtomicInteger의 updateAndGet 메서드를 사용하여 원자적으로 값을 업데이트
-            Function<Integer, Integer> event = queue.take();
-            IntUnaryOperator operator = event::apply;
-            count.updateAndGet(operator);
-        }
-        catch(InterruptedException e){
-            Thread.currentThread().interrupt();
-        }
-    }
-
-    public int show(){
-        return count.get(); // AtomicInteger의 get 메서드를 사용하여 값을 가져옴
-    }
+//    public void addEvent(int value){
+//        try{
+//            // 이 이벤트를 컨슈머가 처리할 당시 count와 value를 더한 값을 출력한다.
+//            queue.put((c) -> c + value);
+//        }
+//        catch(InterruptedException e){
+//            Thread.currentThread().interrupt();
+//        }
+//    }
+//    public Function<Integer, Integer> take(){
+//        try {
+//            return queue.take();
+//        } catch (InterruptedException e) {
+//            throw new RuntimeException(e);
+//        }
+//    }
 }
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
index ae14d8b..a5e8d9a 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
@@ -1,20 +1,32 @@
 package com.thread.concurrency.counter.producerCustomer;
 
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.function.IntUnaryOperator;
 
 public class CounterCustomer {
-    private final CounterBroker counterBroker;
-    private volatile boolean running;
+    private final BlockingQueue<Function<Integer, Integer>> queue;
+    private AtomicInteger count = new AtomicInteger(100); // 스레드 안전성은 synchronized에게 맞기기 때문에 int로 변경.
 
-    public CounterCustomer(CounterBroker counterBroker) {
-        this.counterBroker = counterBroker;
-        this.running = true;
+    public CounterCustomer(BlockingQueue<Function<Integer, Integer>> queue) {
+        this.queue = queue;
     }
-    public void stop() {
-        running = false; // 스레드 종료를 위해 running 플래그를 false로 설정
+
+    public void consumEvent() throws InterruptedException {
+        while(!queue.isEmpty()){
+            Function<Integer,Integer> event = queue.take();
+            IntUnaryOperator operator = event::apply;
+            synchronized (this){
+                System.out.println(count.updateAndGet(operator));
+            }
+        }
     }
-    public void consumEvent(){
-        while(running){
-            counterBroker.consumEvent();
+    public int show(){ // 큐가 비어지는 마지막 순간에 if문이 true가 되어 count를 출력해버린다...
+        while(true){
+            if(queue.isEmpty()){
+                return count.get();
+            }
         }
     }
 }
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
index c35110d..e068f5b 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
@@ -1,13 +1,16 @@
 package com.thread.concurrency.counter.producerCustomer;
 
+import java.util.concurrent.BlockingQueue;
+import java.util.function.Function;
+
 public class CounterProducer {
-    private final CounterBroker counterBroker;
+    private final BlockingQueue<Function<Integer, Integer>> queue;
 
-    public CounterProducer(CounterBroker counterBroker) {
-        this.counterBroker = counterBroker;
+    public CounterProducer(BlockingQueue<Function<Integer, Integer>> queue) {
+        this.queue = queue;
     }
 
-    public void add(int value){
-        counterBroker.addEvent(value);
+    public void add(int value) throws InterruptedException {
+        queue.put((c) -> c + value);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
index 595bac8..3ff8a3f 100644
--- a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
@@ -11,12 +11,8 @@
 
 import java.time.Duration;
 import java.time.LocalTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.*;
+import java.util.function.Function;
 
 public class QueueCounterTest {
     private final int counteNumber = 1;
@@ -25,29 +21,38 @@ public class QueueCounterTest {
     @Test
     @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
     public void 프로듀서_컨슈며_더하기_멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
-        CounterBroker counterBroker = new CounterBroker();
-        CounterCustomer customer = new CounterCustomer(counterBroker);
-        CounterProducer producer = new CounterProducer(counterBroker);
+        BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(1000);
+        CounterCustomer customer = new CounterCustomer(queue);
+        CounterProducer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
-        int initalCount = counterBroker.show();
+        int initalCount = customer.show();
         ExecutorService service = Executors.newFixedThreadPool(15);
         CountDownLatch latch = new CountDownLatch(totalCount);
 
-        // CounterCustomer 스레드 생성 및 비동기로 처리 시작
-        List<CompletableFuture<Void>> futureList = new ArrayList<>();
-        for(int i=0; i<3; i++){
-            futureList.add(CompletableFuture.runAsync(customer::consumEvent));
-        }
         // 프로듀서 스레드 생성
         for (int i = 0; i < totalCount; i++) {
             service.submit(() -> {
-                producer.add(counteNumber);
+                try {
+                    producer.add(counteNumber);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
                 latch.countDown();
             });
         }
+        // CounterCustomer 스레드 생성 및 비동기로 처리 시작
+        for(int i=0; i<3; i++){
+            CompletableFuture.runAsync(()->{
+                try{
+                    customer.consumEvent();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
         latch.await();
 
-        int finalCount = counterBroker.show();
+        int finalCount = customer.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000 + "ms");

From a752e80080b362ec53a2c845c80792e800339d69 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Wed, 3 Apr 2024 12:46:08 +0900
Subject: [PATCH 11/25] =?UTF-8?q?=F0=9F=90=9B=20Fix=20:=20=EC=98=A4?=
 =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 아직 프로듀서-컨슈머 문제해결 못함
2. consumer가 아직 이벤트를 처리 중임을 다른 스레드가 알 수 있거나 이벤트를 전부 끝냈다는 정보를 다른 스레드가 알 수 있게할 필요가 있다.
---
 .../producerCustomer/CounterBroker.java       | 28 ----------------
 .../producerCustomer/CounterConsumer.java     | 33 +++++++++++++++++++
 .../producerCustomer/CounterCustomer.java     | 32 ------------------
 .../counter/CompletableFutureCounterTest.java |  2 +-
 .../concurrency/counter/QueueCounterTest.java | 15 ++++-----
 .../counter/SynchronizedCounterTest.java      |  4 +--
 6 files changed, 42 insertions(+), 72 deletions(-)
 delete mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
 delete mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
deleted file mode 100644
index da1368a..0000000
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterBroker.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.thread.concurrency.counter.producerCustomer;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-public class CounterBroker {
-    // 최대 1000개의 이벤트를 저장할 수 있다.
-    private final BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(1000);
-
-//    public void addEvent(int value){
-//        try{
-//            // 이 이벤트를 컨슈머가 처리할 당시 count와 value를 더한 값을 출력한다.
-//            queue.put((c) -> c + value);
-//        }
-//        catch(InterruptedException e){
-//            Thread.currentThread().interrupt();
-//        }
-//    }
-//    public Function<Integer, Integer> take(){
-//        try {
-//            return queue.take();
-//        } catch (InterruptedException e) {
-//            throw new RuntimeException(e);
-//        }
-//    }
-}
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
new file mode 100644
index 0000000..c80d515
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
@@ -0,0 +1,33 @@
+package com.thread.concurrency.counter.producerCustomer;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.function.IntUnaryOperator;
+
+public class CounterConsumer {
+    private final BlockingQueue<Function<Integer, Integer>> queue;
+    private final AtomicInteger count = new AtomicInteger(100); // 스레드 안전성은 synchronized에게 맞기기 때문에 int로 변경.
+
+    public CounterConsumer(BlockingQueue<Function<Integer, Integer>> queue) {
+        this.queue = queue;
+    }
+
+    public void consumeEvent() throws InterruptedException {
+        while (!queue.isEmpty()) {
+            System.out.println("현재 큐 사이즈 : "+queue.size());
+            Function<Integer, Integer> event = queue.take();
+            IntUnaryOperator operator = event::apply;
+            System.out.println("결과 카운트 : "+count.updateAndGet(operator));
+        }
+    }
+    public int show(){ // 큐가 비어지는 마지막 순간에 if문이 true가 되어 count를 출력해버린다...
+        while(true){
+            if(queue.isEmpty()){
+                int ret = count.get();
+                System.out.println("정답은 ? : "+ret);
+                return ret;
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
deleted file mode 100644
index a5e8d9a..0000000
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterCustomer.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.thread.concurrency.counter.producerCustomer;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
-import java.util.function.IntUnaryOperator;
-
-public class CounterCustomer {
-    private final BlockingQueue<Function<Integer, Integer>> queue;
-    private AtomicInteger count = new AtomicInteger(100); // 스레드 안전성은 synchronized에게 맞기기 때문에 int로 변경.
-
-    public CounterCustomer(BlockingQueue<Function<Integer, Integer>> queue) {
-        this.queue = queue;
-    }
-
-    public void consumEvent() throws InterruptedException {
-        while(!queue.isEmpty()){
-            Function<Integer,Integer> event = queue.take();
-            IntUnaryOperator operator = event::apply;
-            synchronized (this){
-                System.out.println(count.updateAndGet(operator));
-            }
-        }
-    }
-    public int show(){ // 큐가 비어지는 마지막 순간에 if문이 true가 되어 count를 출력해버린다...
-        while(true){
-            if(queue.isEmpty()){
-                return count.get();
-            }
-        }
-    }
-}
diff --git a/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java b/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
index 236bfd9..eac7fef 100644
--- a/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
@@ -37,7 +37,7 @@ public class CompletableFutureCounterTest {
 
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Compltable 테스트가 걸린 시간 : "+dif/1000+"ms");
+        logger.info("여러_더하기_수행_Compltable 테스트가 걸린 시간 : "+dif/1000000+"ms");
         Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
index 3ff8a3f..4903c7b 100644
--- a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
@@ -1,7 +1,6 @@
 package com.thread.concurrency.counter;
 
-import com.thread.concurrency.counter.producerCustomer.CounterBroker;
-import com.thread.concurrency.counter.producerCustomer.CounterCustomer;
+import com.thread.concurrency.counter.producerCustomer.CounterConsumer;
 import com.thread.concurrency.counter.producerCustomer.CounterProducer;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
@@ -22,10 +21,10 @@ public class QueueCounterTest {
     @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
     public void 프로듀서_컨슈며_더하기_멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
         BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(1000);
-        CounterCustomer customer = new CounterCustomer(queue);
+        CounterConsumer consumer = new CounterConsumer(queue);
         CounterProducer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
-        int initalCount = customer.show();
+        int initalCount = consumer.show();
         ExecutorService service = Executors.newFixedThreadPool(15);
         CountDownLatch latch = new CountDownLatch(totalCount);
 
@@ -44,18 +43,16 @@ public class QueueCounterTest {
         for(int i=0; i<3; i++){
             CompletableFuture.runAsync(()->{
                 try{
-                    customer.consumEvent();
+                    consumer.consumeEvent();
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
             });
         }
-        latch.await();
-
-        int finalCount = customer.show();
+        int finalCount = consumer.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000 + "ms");
+        logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
         Assertions.assertEquals(initalCount + totalCount*counteNumber, finalCount);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
index 0c24bec..2cab7da 100644
--- a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
@@ -46,7 +46,7 @@ public class SynchronizedCounterTest {
         int finalCount = counter.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : "+dif/1000+"ms");
+        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : "+dif/1000000+"ms");
         Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
     }
 
@@ -80,7 +80,7 @@ public class SynchronizedCounterTest {
 
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_CompletableFuture 테스트가 걸린 시간 : "+dif/1000+"ms");
+        logger.info("여러_더하기_수행_CompletableFuture 테스트가 걸린 시간 : "+dif/1000000+"ms");
         Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
     }
 }

From a6870ac1bebf60784925f0346931a625acb0fb95 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Wed, 3 Apr 2024 14:42:41 +0900
Subject: [PATCH 12/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=E2=9C=85=20?=
 =?UTF-8?q?Test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 보기 쉽게 각 테스트들 정리
---
 .../concurrency/counter/AtomicCounter.java    | 18 +++++++
 .../counter/AtomicCounterTest.java            | 48 +++++++++++++++++++
 .../counter/CompletableFutureCounterTest.java | 21 ++++----
 .../counter/SynchronizedCounterTest.java      | 25 +++++-----
 4 files changed, 90 insertions(+), 22 deletions(-)
 create mode 100644 src/main/java/com/thread/concurrency/counter/AtomicCounter.java
 create mode 100644 src/test/java/com/thread/concurrency/counter/AtomicCounterTest.java

diff --git a/src/main/java/com/thread/concurrency/counter/AtomicCounter.java b/src/main/java/com/thread/concurrency/counter/AtomicCounter.java
new file mode 100644
index 0000000..1fdf3bd
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/AtomicCounter.java
@@ -0,0 +1,18 @@
+package com.thread.concurrency.counter;
+
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class AtomicCounter implements Counter{
+    private final AtomicInteger count = new AtomicInteger(100);
+    @Override
+    public void add(int value) {
+        count.addAndGet(value);
+    }
+    @Override
+    public int show() {
+        return count.get();
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/AtomicCounterTest.java b/src/test/java/com/thread/concurrency/counter/AtomicCounterTest.java
new file mode 100644
index 0000000..1a55a18
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/counter/AtomicCounterTest.java
@@ -0,0 +1,48 @@
+package com.thread.concurrency.counter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@SpringBootTest
+public class AtomicCounterTest {
+    private final int counteNumber = 1;
+    private final int totalCount = 5000000;
+    private final int maxThreadNumber = 15;
+    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
+    @Autowired
+    AtomicCounter counter;
+
+    @Test
+    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
+    public void 여러_더하기_수행_Executor() throws InterruptedException {
+
+        LocalTime lt1 = LocalTime.now();
+        int initalCount = counter.show();
+
+        ExecutorService service = Executors.newFixedThreadPool(maxThreadNumber);
+        CountDownLatch latch = new CountDownLatch(totalCount);
+        for (int i = 0; i < totalCount; i++) {
+            service.submit(() -> {
+                counter.add(counteNumber);
+                latch.countDown();
+            });
+        }
+        latch.await();
+        int finalCount = counter.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java b/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
index eac7fef..86162ae 100644
--- a/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/CompletableFutureCounterTest.java
@@ -1,9 +1,11 @@
 package com.thread.concurrency.counter;
 
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import java.time.Duration;
@@ -14,17 +16,21 @@
 
 @SpringBootTest
 public class CompletableFutureCounterTest {
+
     private final int counteNumber = 1;
-    private final int totalCount = 10000;
+    private final int totalCount = 5000;
+    private final int maxThreadNumber = 15;
     private static final Logger logger = LoggerFactory.getLogger(CompletableFutureCounterTest.class);
 
+    @Autowired
+    CompletableFutureCounter counter;
     @Test
-    public void 여러_더하기_수행_Compltable() throws InterruptedException {
-        SynchronizedCounter counter = new SynchronizedCounter();
+    @DisplayName("CompletableFuture로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
+    public void 여러_더하기_수행_Executor() throws InterruptedException {
         LocalTime lt1 = LocalTime.now();
-
         int initalCount = counter.show();
-        ExecutorService service = Executors.newFixedThreadPool(15);
+
+        ExecutorService service = Executors.newFixedThreadPool(maxThreadNumber);
         CountDownLatch latch = new CountDownLatch(totalCount);
         for (int i = 0; i < totalCount; i++) {
             service.submit(() -> {
@@ -34,10 +40,9 @@ public class CompletableFutureCounterTest {
         }
         latch.await();
         int finalCount = counter.show();
-
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Compltable 테스트가 걸린 시간 : "+dif/1000000+"ms");
-        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
+        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
index 2cab7da..9630e2f 100644
--- a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
@@ -20,21 +20,21 @@
 public class SynchronizedCounterTest {
 
     private final int counteNumber = 1;
-    private final int totalCount = 10000;
+    private final int totalCount = 5000000;
+    private final int maxThreadNumber = 15;
     private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
 
-    /**
-     * 실행 완료까지 871ms 정도 소요
-     * @throws InterruptedException
-     */
+    @Autowired
+    SynchronizedCounter counter;
+
     @Test
-    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
+    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
     public void 여러_더하기_수행_Executor() throws InterruptedException {
-        SynchronizedCounter counter = new SynchronizedCounter();
+
         LocalTime lt1 = LocalTime.now();
         int initalCount = counter.show();
-        int numberOfThreads = 15;
-        ExecutorService service = Executors.newFixedThreadPool(15);
+
+        ExecutorService service = Executors.newFixedThreadPool(maxThreadNumber);
         CountDownLatch latch = new CountDownLatch(totalCount);
         for (int i = 0; i < totalCount; i++) {
             service.submit(() -> {
@@ -46,13 +46,10 @@ public class SynchronizedCounterTest {
         int finalCount = counter.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : "+dif/1000000+"ms");
-        Assertions.assertEquals(initalCount+totalCount*counteNumber, finalCount);
+        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
     }
 
-    /**
-     * 실행 완료까지 1061ms 소요
-     */
     @Test
     @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
     public void 여러_더하기_수행_CompletableFuture() {

From fd6771133321bef26854806c0ee8c165f7e5839f Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Thu, 4 Apr 2024 22:37:35 +0900
Subject: [PATCH 13/25] =?UTF-8?q?=F0=9F=93=9D=20Docs=20:=20=EB=AC=B8?=
 =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 밴치마크 결과 Readme에 작성
---
 README.md                                       | 17 ++++++++++++++++-
 .../thread/concurrency/AtomicCounterTest.java   |  7 +++++--
 .../CompletableFutureCounterTest.java           |  7 +++++--
 .../concurrency/SynchronizedCounterTest.java    |  5 ++++-
 4 files changed, 30 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index 86633b3..e2b66a9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,16 @@
-# spring-thread-concurrency
\ No newline at end of file
+# spring-thread-concurrency
+
+## 카운터 밴치 마크
+
+**실험 환경**
+칩 : Apple M2 8코어
+메모리 : 16GB
+
+| 5회 평균 | 최대 스레드 개수 | 전체 요청 수 | 테스트 시간(ms) | 메모리 사용량(MB) |
+| --- | --- | --- | --- | --- |
+| AtomicCounter | 9 | 5,000,000 | 321.15 | 12.82 |
+| AtomicCounter | 15 | 5,000,000 | 419.33 | 12.16 |
+| CompletableFutureCounter | 9 | 5,000,000 | 885.95 | 11.78 |
+| CompletableFutureCounter | 15 | 5,000,000 | 939.16 | 11.78 |
+| SynchronizedCounter | 9 | 5,000,000 | 398.63 | 12.32 |
+| SynchronizedCounter | 15 | 5,000,000 | 495.99 | 11.86 |
diff --git a/src/test/java/com/thread/concurrency/AtomicCounterTest.java b/src/test/java/com/thread/concurrency/AtomicCounterTest.java
index d104356..a9a4e45 100644
--- a/src/test/java/com/thread/concurrency/AtomicCounterTest.java
+++ b/src/test/java/com/thread/concurrency/AtomicCounterTest.java
@@ -19,7 +19,7 @@
 public class AtomicCounterTest {
     private final int counteNumber = 1;
     private final int totalCount = 5000000;
-    private final int maxThreadNumber = 15;
+    private final int maxThreadNumber = 9;
     private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
     @Autowired
     AtomicCounter counter;
@@ -27,7 +27,7 @@ public class AtomicCounterTest {
     @Test
     @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
     public void 여러_더하기_수행_Executor() throws InterruptedException {
-
+        Runtime.getRuntime().gc();
         LocalTime lt1 = LocalTime.now();
         int initalCount = counter.show();
 
@@ -44,6 +44,9 @@ public class AtomicCounterTest {
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Runtime.getRuntime().gc();
+        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+        logger.info("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
         Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java b/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java
index 3999676..d0ebd57 100644
--- a/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java
+++ b/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java
@@ -21,8 +21,8 @@
 public class CompletableFutureCounterTest {
 
     private final int counteNumber = 1;
-    private final int totalCount = 5000;
-    private final int maxThreadNumber = 15;
+    private final int totalCount = 5000000;
+    private final int maxThreadNumber = 9;
     private static final Logger logger = LoggerFactory.getLogger(CompletableFutureCounterTest.class);
 
     @Autowired
@@ -46,6 +46,9 @@ public class CompletableFutureCounterTest {
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Runtime.getRuntime().gc();
+        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+        logger.info("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
         Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java
index 7b073b5..074e966 100644
--- a/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java
+++ b/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java
@@ -20,7 +20,7 @@ public class SynchronizedCounterTest {
 
     private final int counteNumber = 1;
     private final int totalCount = 5000000;
-    private final int maxThreadNumber = 15;
+    private final int maxThreadNumber = 9;
     private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
 
     @Autowired
@@ -46,6 +46,9 @@ public class SynchronizedCounterTest {
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Runtime.getRuntime().gc();
+        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+        logger.info("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
         Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
     }
 }

From f98cdbdc94ac8311d2070d171d4cb9e6d27776e2 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Fri, 5 Apr 2024 10:01:09 +0900
Subject: [PATCH 14/25] =?UTF-8?q?=E2=9C=85=20Test=20:=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. CounterConsumer의 System.out.println을 주석하면 데드락이 걸리는 건지 테스트가 진행이 안되는 문제.,.
---
 .../producerCustomer/CounterConsumer.java     | 22 +++++++++++--------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
index c80d515..b665af0 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
@@ -14,19 +14,23 @@ public CounterConsumer(BlockingQueue<Function<Integer, Integer>> queue) {
     }
 
     public void consumeEvent() throws InterruptedException {
-        while (!queue.isEmpty()) {
-            System.out.println("현재 큐 사이즈 : "+queue.size());
-            Function<Integer, Integer> event = queue.take();
-            IntUnaryOperator operator = event::apply;
-            System.out.println("결과 카운트 : "+count.updateAndGet(operator));
+        synchronized (count){
+            while (!queue.isEmpty()) {
+//                System.out.println("현재 큐 사이즈 : "+queue.size());
+                Function<Integer, Integer> event = queue.take();
+                IntUnaryOperator operator = event::apply;
+                count.updateAndGet(operator);
+//                System.out.println("결과 카운트 : "+);
+            }
+
         }
     }
     public int show(){ // 큐가 비어지는 마지막 순간에 if문이 true가 되어 count를 출력해버린다...
         while(true){
-            if(queue.isEmpty()){
-                int ret = count.get();
-                System.out.println("정답은 ? : "+ret);
-                return ret;
+            synchronized (count){
+                if(queue.isEmpty()){
+                    return count.get();
+                }
             }
         }
     }

From 8eba8ee7457f809a8a17b55d278115280ab739e9 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sat, 6 Apr 2024 23:18:10 +0900
Subject: [PATCH 15/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=E2=9C=85=20?=
 =?UTF-8?q?Test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 테스트가 진행되지 않는 이유를 찾음
	프로듀서가 큐에 작업을 넣는 속도 보다 컨슈머가 작업을 처리하는 속도가 더 빠름
	그렇기 때문에 전체 작업을 하기도 전에 큐가 다 비어버림
	큐가 빈것을 인지한 컨슈머가 작업을 끝냄.
	더 이상 작업이 진행되지 않음

To Do
프로듀서의 작업 추가 속도를 올리기
컨슈머의 작업 처리 속도를 늦추기 혹은 다 처리해도 대기하기
---
 .../producerCustomer/CounterConsumer.java     | 29 +++----
 .../producerCustomer/CounterProducer.java     |  8 +-
 .../concurrency/counter/QueueCounterTest.java | 82 +++++++++++++++++--
 3 files changed, 90 insertions(+), 29 deletions(-)

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
index b665af0..2108ebd 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
@@ -2,35 +2,32 @@
 
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
 import java.util.function.IntUnaryOperator;
+import java.util.function.LongUnaryOperator;
 
 public class CounterConsumer {
-    private final BlockingQueue<Function<Integer, Integer>> queue;
-    private final AtomicInteger count = new AtomicInteger(100); // 스레드 안전성은 synchronized에게 맞기기 때문에 int로 변경.
+    private final BlockingQueue<Long> queue;
+    private final AtomicLong count = new AtomicLong(0); // 스레드 안전성은 synchronized에게 맞기기 때문에 int로 변경.
 
-    public CounterConsumer(BlockingQueue<Function<Integer, Integer>> queue) {
+    public CounterConsumer(BlockingQueue<Long> queue) {
         this.queue = queue;
     }
 
     public void consumeEvent() throws InterruptedException {
-        synchronized (count){
-            while (!queue.isEmpty()) {
-//                System.out.println("현재 큐 사이즈 : "+queue.size());
-                Function<Integer, Integer> event = queue.take();
-                IntUnaryOperator operator = event::apply;
-                count.updateAndGet(operator);
-//                System.out.println("결과 카운트 : "+);
-            }
 
+        while (!queue.isEmpty()) {
+            System.out.println(Thread.currentThread().getName()+"은 현재 큐 사이즈 : "+queue.size());
+            Long value = queue.take();
+//            count.addAndGet(value);
+            System.out.println("결과 카운트 : "+count.addAndGet(value));
         }
     }
-    public int show(){ // 큐가 비어지는 마지막 순간에 if문이 true가 되어 count를 출력해버린다...
+    public Long show(){
         while(true){
-            synchronized (count){
-                if(queue.isEmpty()){
-                    return count.get();
-                }
+            if(queue.isEmpty()){
+                return count.get();
             }
         }
     }
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
index e068f5b..74df1d7 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
@@ -4,13 +4,13 @@
 import java.util.function.Function;
 
 public class CounterProducer {
-    private final BlockingQueue<Function<Integer, Integer>> queue;
+    private final BlockingQueue<Long> queue;
 
-    public CounterProducer(BlockingQueue<Function<Integer, Integer>> queue) {
+    public CounterProducer(BlockingQueue<Long> queue) {
         this.queue = queue;
     }
 
-    public void add(int value) throws InterruptedException {
-        queue.put((c) -> c + value);
+    public void add(long value) throws InterruptedException {
+        queue.put(value);
     }
 }
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
index 4903c7b..5c0d14c 100644
--- a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
@@ -10,29 +10,34 @@
 
 import java.time.Duration;
 import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.*;
 import java.util.function.Function;
 
 public class QueueCounterTest {
     private final int counteNumber = 1;
-    private final int totalCount = 10000;
+    private final int totalCount = 50000000; // Integer.MAX_VALUE;
+    private final int nThread = 15;
     private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
     @Test
     @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
     public void 프로듀서_컨슈며_더하기_멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
-        BlockingQueue<Function<Integer, Integer>> queue = new LinkedBlockingQueue<>(1000);
+        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
         CounterConsumer consumer = new CounterConsumer(queue);
         CounterProducer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
-        int initalCount = consumer.show();
-        ExecutorService service = Executors.newFixedThreadPool(15);
+        Long initalCount = consumer.show();
+        ExecutorService service = Executors.newFixedThreadPool(nThread);
         CountDownLatch latch = new CountDownLatch(totalCount);
 
         // 프로듀서 스레드 생성
-        for (int i = 0; i < totalCount; i++) {
+        for (int i = 0; i < nThread; i++) {
             service.submit(() -> {
                 try {
-                    producer.add(counteNumber);
+                    for(int j=0; j<totalCount/nThread; j++){
+                        producer.add(counteNumber);
+                    }
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
@@ -40,7 +45,7 @@ public class QueueCounterTest {
             });
         }
         // CounterCustomer 스레드 생성 및 비동기로 처리 시작
-        for(int i=0; i<3; i++){
+        for(int i=0; i<nThread; i++){
             CompletableFuture.runAsync(()->{
                 try{
                     consumer.consumeEvent();
@@ -49,10 +54,69 @@ public class QueueCounterTest {
                 }
             });
         }
-        int finalCount = consumer.show();
+        latch.await();
+//        Long finalCount = consumer.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+//        Assertions.assertEquals(initalCount + totalCount*counteNumber, finalCount);
+    }
+
+    @Test
+    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
+    public void 여러_더하기_수행_CompletableFuture() throws InterruptedException {
+        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
+        CounterConsumer consumer = new CounterConsumer(queue);
+        CounterProducer producer = new CounterProducer(queue);
+        LocalTime lt1 = LocalTime.now();
+        Long initalCount = consumer.show();
+        ExecutorService service = Executors.newFixedThreadPool(nThread);
+        CountDownLatch latch = new CountDownLatch(nThread);
+
+        // 프로듀서 스레드 생성
+        for (int i = 0; i < nThread; i++) {
+            service.submit(() -> {
+                try {
+                    for(int j=0; j<totalCount/nThread; j++){
+                        producer.add(counteNumber);
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                latch.countDown();
+            });
+        }
+        // CounterCustomer 스레드 생성 및 비동기로 처리 시작
+        List<CompletableFuture<Void>> tasks = new ArrayList<>();
+        for(int i=0; i<3; i++){
+            tasks.add(CompletableFuture.runAsync(()->{
+                try{
+                    consumer.consumeEvent();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }));
+        }
+
+        CompletableFuture<List<Void>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
+        for (CompletableFuture<Void> future : tasks) {
+            aggregate = aggregate.thenCompose(list -> {
+                try {
+                    list.add(future.get());
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+                return CompletableFuture.completedFuture(list);
+            });
+        }
+        aggregate.join(); // 전체 비동기 결과 집계
+        System.out.println("컨슈머 작업 끝남");
+        latch.await();
+        System.out.println("프로듀서 작업 끝남");
+        Long finalCount = consumer.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-        Assertions.assertEquals(initalCount + totalCount*counteNumber, finalCount);
+        Assertions.assertEquals(initalCount + (totalCount/nThread)*nThread*counteNumber, finalCount);
     }
 }

From c763bb4cc47411c728ebcf29e9cfaa919b714d66 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 11:57:36 +0900
Subject: [PATCH 16/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=E2=9C=85=20?=
 =?UTF-8?q?Test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 프로듀서-컨슈머 패턴 구현 완료
2. 요구사항인 Integer.MAX_VALUE만큼 더하기 완료

To Do

메모리 누수 때문에 메모리 덤프가 생성됨
메모리 누수의 원인을 찾고 해결해야함
---
 .../producerCustomer/CounterConsumer.java     | 21 +++---
 .../concurrency/counter/QueueCounterTest.java | 72 ++++++++++---------
 2 files changed, 50 insertions(+), 43 deletions(-)

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
index 2108ebd..61f7c5c 100644
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
+++ b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
@@ -1,6 +1,8 @@
 package com.thread.concurrency.counter.producerCustomer;
 
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
@@ -9,19 +11,22 @@
 
 public class CounterConsumer {
     private final BlockingQueue<Long> queue;
-    private final AtomicLong count = new AtomicLong(0); // 스레드 안전성은 synchronized에게 맞기기 때문에 int로 변경.
+    private final AtomicLong count = new AtomicLong(0);
 
     public CounterConsumer(BlockingQueue<Long> queue) {
         this.queue = queue;
     }
 
-    public void consumeEvent() throws InterruptedException {
-
-        while (!queue.isEmpty()) {
-            System.out.println(Thread.currentThread().getName()+"은 현재 큐 사이즈 : "+queue.size());
-            Long value = queue.take();
-//            count.addAndGet(value);
-            System.out.println("결과 카운트 : "+count.addAndGet(value));
+    public void consumeEvent(long timeout, TimeUnit unit) throws InterruptedException {
+        while (true) {
+//            System.out.println(Thread.currentThread().getName()+"은 현재 큐 사이즈 : "+queue.size());
+            Long value = queue.poll(timeout, unit);
+            if(value == null){
+//                System.out.println(Thread.currentThread().getName()+" 끝났으!!");
+                break;
+            }
+            count.addAndGet(value);
+//            System.out.println("결s과 카운트 : "+count.addAndGet(value));
         }
     }
     public Long show(){
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
index 5c0d14c..0d170b0 100644
--- a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
@@ -17,25 +17,28 @@
 
 public class QueueCounterTest {
     private final int counteNumber = 1;
-    private final int totalCount = 50000000; // Integer.MAX_VALUE;
+    private final int totalCount = Integer.MAX_VALUE;
     private final int nThread = 15;
     private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
+
     @Test
-    @DisplayName("producer consumer 패턴을 이용해서 더하기 이벤트 발생 스레드와 더하기 이벤트 처리 스레드를 분리하자")
-    public void 프로듀서_컨슈며_더하기_멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
+    @DisplayName("멀티 프로듀서 싱글 컨슈머")
+    public void 멀티_프로듀서_싱글_컨슈머() throws InterruptedException {
         BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
         CounterConsumer consumer = new CounterConsumer(queue);
         CounterProducer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
         Long initalCount = consumer.show();
         ExecutorService service = Executors.newFixedThreadPool(nThread);
-        CountDownLatch latch = new CountDownLatch(totalCount);
+        CountDownLatch latch = new CountDownLatch(nThread);
 
         // 프로듀서 스레드 생성
         for (int i = 0; i < nThread; i++) {
+//            int finalI = i;
             service.submit(() -> {
                 try {
-                    for(int j=0; j<totalCount/nThread; j++){
+                    for(int j=0; j<(totalCount/nThread); j++){
+//                        System.out.println(Thread.currentThread().getName()+"은 작업 추가 "+ finalI +" "+j);
                         producer.add(counteNumber);
                     }
                 } catch (InterruptedException e) {
@@ -45,26 +48,31 @@ public class QueueCounterTest {
             });
         }
         // CounterCustomer 스레드 생성 및 비동기로 처리 시작
-        for(int i=0; i<nThread; i++){
-            CompletableFuture.runAsync(()->{
-                try{
-                    consumer.consumeEvent();
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            });
+        CompletableFuture<Void> task = CompletableFuture.runAsync(()->{
+            try{
+                consumer.consumeEvent(10, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        try {
+            task.get();
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
         }
+        System.out.println("컨슈머 작업 끝남");
         latch.await();
-//        Long finalCount = consumer.show();
+        System.out.println("프로듀서 작업 끝남");
+
+        Long finalCount = consumer.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-//        Assertions.assertEquals(initalCount + totalCount*counteNumber, finalCount);
+        Assertions.assertEquals(initalCount + (totalCount/nThread)*nThread*counteNumber, finalCount);
     }
-
     @Test
-    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기. 활동성 문제 예상")
-    public void 여러_더하기_수행_CompletableFuture() throws InterruptedException {
+    @DisplayName("멀티 프로듀서 멀티 컨슈머")
+    public void 멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
         BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
         CounterConsumer consumer = new CounterConsumer(queue);
         CounterProducer producer = new CounterProducer(queue);
@@ -73,11 +81,10 @@ public class QueueCounterTest {
         ExecutorService service = Executors.newFixedThreadPool(nThread);
         CountDownLatch latch = new CountDownLatch(nThread);
 
-        // 프로듀서 스레드 생성
         for (int i = 0; i < nThread; i++) {
             service.submit(() -> {
                 try {
-                    for(int j=0; j<totalCount/nThread; j++){
+                    for(int j=0; j<(totalCount/nThread); j++){
                         producer.add(counteNumber);
                     }
                 } catch (InterruptedException e) {
@@ -89,27 +96,22 @@ public class QueueCounterTest {
         // CounterCustomer 스레드 생성 및 비동기로 처리 시작
         List<CompletableFuture<Void>> tasks = new ArrayList<>();
         for(int i=0; i<3; i++){
-            tasks.add(CompletableFuture.runAsync(()->{
+            tasks.add( CompletableFuture.runAsync(()->{
                 try{
-                    consumer.consumeEvent();
+                    consumer.consumeEvent(1, TimeUnit.SECONDS);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
             }));
         }
-
-        CompletableFuture<List<Void>> aggregate = CompletableFuture.completedFuture(new ArrayList<>());
-        for (CompletableFuture<Void> future : tasks) {
-            aggregate = aggregate.thenCompose(list -> {
-                try {
-                    list.add(future.get());
-                } catch (InterruptedException | ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-                return CompletableFuture.completedFuture(list);
-            });
-        }
-        aggregate.join(); // 전체 비동기 결과 집계
+        tasks.forEach((t) -> {
+            try {
+//                System.out.println("적당히 돌아가는거 확인합닛다.");
+                t.get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        });
         System.out.println("컨슈머 작업 끝남");
         latch.await();
         System.out.println("프로듀서 작업 끝남");

From ada5ae2df53739aab96f8edfce6a060a4636dd79 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:27:18 +0900
Subject: [PATCH 17/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=E2=9C=85=20?=
 =?UTF-8?q?Test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80=201.=20=ED=94=84=EB=A1=9C=EB=93=80=EC=84=9C?=
 =?UTF-8?q?=20=EC=BB=A8=EC=8A=88=EB=A8=B8=20pr=20=EC=A4=80=EB=B9=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../counter/queueCounter/CounterConsumer.java |  34 ++++++
 .../counter/queueCounter/CounterProducer.java |  15 +++
 .../thread/concurrency/AtomicCounterTest.java |  52 --------
 .../CompletableFutureCounterTest.java         |  54 --------
 .../com/thread/concurrency/CounterTest.java   |  54 ++++++++
 .../concurrency/SynchronizedCounterTest.java  |  54 --------
 .../concurrency/counter/CounterTest.java      |   2 +-
 .../queueCounter/QueueCounterTest.java        | 115 ++++++++++++++++++
 8 files changed, 219 insertions(+), 161 deletions(-)
 create mode 100644 src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java
 delete mode 100644 src/test/java/com/thread/concurrency/AtomicCounterTest.java
 delete mode 100644 src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java
 create mode 100644 src/test/java/com/thread/concurrency/CounterTest.java
 delete mode 100644 src/test/java/com/thread/concurrency/SynchronizedCounterTest.java
 create mode 100644 src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java

diff --git a/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java
new file mode 100644
index 0000000..c12edf1
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java
@@ -0,0 +1,34 @@
+package com.thread.concurrency.counter.queueCounter;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class CounterConsumer {
+    private final BlockingQueue<Long> queue;
+    private final AtomicLong count = new AtomicLong(0);
+
+    public CounterConsumer(BlockingQueue<Long> queue) {
+        this.queue = queue;
+    }
+
+    public void consumeEvent(long timeout, TimeUnit unit) throws InterruptedException {
+        while (true) {
+//            System.out.println(Thread.currentThread().getName()+"은 현재 큐 사이즈 : "+queue.size());
+            Long value = queue.poll(timeout, unit);
+            if(value == null){
+//                System.out.println(Thread.currentThread().getName()+" 끝났으!!");
+                break;
+            }
+            count.addAndGet(value);
+//            System.out.println("결s과 카운트 : "+count.addAndGet(value));
+        }
+    }
+    public Long show(){
+        while(true){
+            if(queue.isEmpty()){
+                return count.get();
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java
new file mode 100644
index 0000000..126e608
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java
@@ -0,0 +1,15 @@
+package com.thread.concurrency.counter.queueCounter;
+
+import java.util.concurrent.BlockingQueue;
+
+public class CounterProducer {
+    private final BlockingQueue<Long> queue;
+
+    public CounterProducer(BlockingQueue<Long> queue) {
+        this.queue = queue;
+    }
+
+    public void add(long value) throws InterruptedException {
+        queue.put(value);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/AtomicCounterTest.java b/src/test/java/com/thread/concurrency/AtomicCounterTest.java
deleted file mode 100644
index a9a4e45..0000000
--- a/src/test/java/com/thread/concurrency/AtomicCounterTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.thread.concurrency;
-
-import com.thread.concurrency.counter.AtomicCounter;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import java.time.Duration;
-import java.time.LocalTime;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-@SpringBootTest
-public class AtomicCounterTest {
-    private final int counteNumber = 1;
-    private final int totalCount = 5000000;
-    private final int maxThreadNumber = 9;
-    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
-    @Autowired
-    AtomicCounter counter;
-
-    @Test
-    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
-    public void 여러_더하기_수행_Executor() throws InterruptedException {
-        Runtime.getRuntime().gc();
-        LocalTime lt1 = LocalTime.now();
-        int initalCount = counter.show();
-
-        ExecutorService service = Executors.newFixedThreadPool(maxThreadNumber);
-        CountDownLatch latch = new CountDownLatch(totalCount);
-        for (int i = 0; i < totalCount; i++) {
-            service.submit(() -> {
-                counter.add(counteNumber);
-                latch.countDown();
-            });
-        }
-        latch.await();
-        int finalCount = counter.show();
-        LocalTime lt2 = LocalTime.now();
-        long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
-        Runtime.getRuntime().gc();
-        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-        logger.info("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
-        Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
-    }
-}
diff --git a/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java b/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java
deleted file mode 100644
index d0ebd57..0000000
--- a/src/test/java/com/thread/concurrency/CompletableFutureCounterTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.thread.concurrency;
-
-import com.thread.concurrency.counter.CompletableFutureCounter;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.context.annotation.Bean;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-
-import java.time.Duration;
-import java.time.LocalTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.*;
-
-@SpringBootTest
-public class CompletableFutureCounterTest {
-
-    private final int counteNumber = 1;
-    private final int totalCount = 5000000;
-    private final int maxThreadNumber = 9;
-    private static final Logger logger = LoggerFactory.getLogger(CompletableFutureCounterTest.class);
-
-    @Autowired
-    CompletableFutureCounter counter;
-    @Test
-    @DisplayName("CompletableFuture로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
-    public void 여러_더하기_수행_Executor() throws InterruptedException {
-        LocalTime lt1 = LocalTime.now();
-        int initalCount = counter.show();
-
-        ExecutorService service = Executors.newFixedThreadPool(maxThreadNumber);
-        CountDownLatch latch = new CountDownLatch(totalCount);
-        for (int i = 0; i < totalCount; i++) {
-            service.submit(() -> {
-                counter.add(counteNumber);
-                latch.countDown();
-            });
-        }
-        latch.await();
-        int finalCount = counter.show();
-        LocalTime lt2 = LocalTime.now();
-        long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
-        Runtime.getRuntime().gc();
-        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-        logger.info("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
-        Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
-    }
-}
diff --git a/src/test/java/com/thread/concurrency/CounterTest.java b/src/test/java/com/thread/concurrency/CounterTest.java
new file mode 100644
index 0000000..d88727d
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/CounterTest.java
@@ -0,0 +1,54 @@
+package com.thread.concurrency;
+
+import com.thread.concurrency.counter.*;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Stream;
+
+@SpringBootTest
+public class CounterTest {
+    private final int valueToAdd = 1;
+    private final int nAddsPerThread = 50000000;
+    private final int nThreads = 9;
+
+    public static Stream<Counter> counterProvider() {
+        return Stream.of(new AtomicCounter(), new CompletableFutureCounter(), new SynchronizedCounter());
+    }
+
+    @ParameterizedTest
+    @MethodSource("counterProvider")
+    @DisplayName("스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
+    public void 여러_더하기_수행_Executor(Counter counter) throws InterruptedException {
+        LocalTime lt1 = LocalTime.now();
+        int initalCount = counter.show();
+
+        ExecutorService service = Executors.newFixedThreadPool(nThreads);
+        CountDownLatch latch = new CountDownLatch(nThreads);
+        for (int i = 0; i < nThreads; i++) {
+            service.submit(() -> {
+                for(int j=0; j<nAddsPerThread; j++){
+                    counter.add(valueToAdd);
+                }
+                latch.countDown();
+            });
+        }
+        latch.await();
+        int finalCount = counter.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        System.out.println("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Runtime.getRuntime().gc();
+        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+        System.out.println("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
+        Assertions.assertEquals(initalCount + nThreads*nAddsPerThread*valueToAdd, finalCount);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java
deleted file mode 100644
index 074e966..0000000
--- a/src/test/java/com/thread/concurrency/SynchronizedCounterTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.thread.concurrency;
-
-import com.thread.concurrency.counter.SynchronizedCounter;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import java.time.Duration;
-import java.time.LocalTime;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-@SpringBootTest
-public class SynchronizedCounterTest {
-
-    private final int counteNumber = 1;
-    private final int totalCount = 5000000;
-    private final int maxThreadNumber = 9;
-    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
-
-    @Autowired
-    SynchronizedCounter counter;
-
-    @Test
-    @DisplayName("synchronized로 스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
-    public void 여러_더하기_수행_Executor() throws InterruptedException {
-
-        LocalTime lt1 = LocalTime.now();
-        int initalCount = counter.show();
-
-        ExecutorService service = Executors.newFixedThreadPool(maxThreadNumber);
-        CountDownLatch latch = new CountDownLatch(totalCount);
-        for (int i = 0; i < totalCount; i++) {
-            service.submit(() -> {
-                counter.add(counteNumber);
-                latch.countDown();
-            });
-        }
-        latch.await();
-        int finalCount = counter.show();
-        LocalTime lt2 = LocalTime.now();
-        long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
-        Runtime.getRuntime().gc();
-        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
-        logger.info("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
-        Assertions.assertEquals(initalCount + totalCount * counteNumber, finalCount);
-    }
-}
diff --git a/src/test/java/com/thread/concurrency/counter/CounterTest.java b/src/test/java/com/thread/concurrency/counter/CounterTest.java
index 3493483..19a8a40 100644
--- a/src/test/java/com/thread/concurrency/counter/CounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/CounterTest.java
@@ -34,7 +34,7 @@ private static void assertThen(Counter counter, int expectedValue, int actualVal
     public void stressTest(Counter counter) throws InterruptedException {
         int initialValue = counter.show();
         int nThreads = 100;
-        int nAddsPerThread = 1000;
+        int nAddsPerThread = 100000;
         int valueToAdd = 1;
         int expectedValue = initialValue + nThreads * nAddsPerThread * valueToAdd;
 
diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
new file mode 100644
index 0000000..a6f231a
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -0,0 +1,115 @@
+package com.thread.concurrency.queueCounter;
+
+import com.thread.concurrency.counter.queueCounter.CounterConsumer;
+import com.thread.concurrency.counter.queueCounter.CounterProducer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.concurrent.*;
+
+public class QueueCounterTest {
+    private final int valueToAdd = 1;
+    private final int nAddsPerThread = 50000000;
+    private final int nThreads = 9;
+    private final long timeout = 1;
+    private final TimeUnit unit = TimeUnit.SECONDS;
+
+    @Test
+    @DisplayName("멀티 프로듀서 싱글 컨슈머")
+    public void 멀티_프로듀서_싱글_컨슈머() throws InterruptedException {
+        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
+        CounterConsumer consumer = new CounterConsumer(queue);
+        CounterProducer producer = new CounterProducer(queue);
+        LocalTime lt1 = LocalTime.now();
+        Long initalCount = consumer.show();
+        ExecutorService producerService = Executors.newFixedThreadPool(nThreads);
+        ExecutorService consumerService = Executors.newFixedThreadPool(nThreads);
+        CountDownLatch producerLatch = new CountDownLatch(nThreads);
+        CountDownLatch consumerLatch = new CountDownLatch(1);
+
+        // 프로듀서 스레드 생성
+        for (int i = 0; i < nThreads; i++) {
+            producerService.submit(() -> {
+                try {
+                    for(int j=0; j<nAddsPerThread; j++){
+                        producer.add(valueToAdd);
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                producerLatch.countDown();
+            });
+        }
+
+        // 컨슈머 스레드 생성
+        consumerService.submit(() -> {
+            try {
+                consumer.consumeEvent(timeout, unit);
+                consumerLatch.countDown();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        consumerLatch.await();
+        System.out.println("컨슈머 작업 끝남");
+        producerLatch.await();
+        System.out.println("프로듀서 작업 끝남");
+
+        Long finalCount = consumer.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
+    }
+    @Test
+    @DisplayName("멀티 프로듀서 멀티 컨슈머")
+    public void 멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
+        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
+        CounterConsumer consumer = new CounterConsumer(queue);
+        CounterProducer producer = new CounterProducer(queue);
+        LocalTime lt1 = LocalTime.now();
+        Long initalCount = consumer.show();
+        ExecutorService producerService = Executors.newFixedThreadPool(nThreads);
+        ExecutorService consumerService = Executors.newFixedThreadPool(nThreads);
+        CountDownLatch producerLatch = new CountDownLatch(nThreads);
+        CountDownLatch consumerLatch = new CountDownLatch(nThreads);
+
+        // 프로듀서 스레드 생성
+        for (int i = 0; i < nThreads; i++) {
+            producerService.submit(() -> {
+                try {
+                    for(int j=0; j<nAddsPerThread; j++){
+                        producer.add(valueToAdd);
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                producerLatch.countDown();
+            });
+        }
+        // 컨슈머 스레드 생성
+        for (int i = 0; i < nThreads; i++) {
+            consumerService.submit(() -> {
+                try {
+                    consumer.consumeEvent(timeout, unit);
+                    consumerLatch.countDown();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
+        consumerLatch.await();
+        System.out.println("컨슈머 작업 끝남");
+        producerLatch.await();
+        System.out.println("프로듀서 작업 끝남");
+
+        Long finalCount = consumer.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
+    }
+}

From b286a26ebc524756649d1596b008b40256b6b601 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:32:09 +0900
Subject: [PATCH 18/25] =?UTF-8?q?=E2=9C=85=20Test=20:=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 프로듀서 컨슈머 패턴 테스트 정리
---
 .../com/thread/concurrency/CounterTest.java   |  57 +++++++++
 .../{ => async}/AsyncControllerTest.java      |   2 +-
 .../{ => async}/AsyncServiceTest.java         |   2 +-
 ...est.java => BlockingQueueCounterTest.java} |   5 +-
 .../counter/SynchronizedCounterTest.java      |   2 -
 .../queueCounter/QueueCounterTest.java        | 116 ++++++++++++++++++
 6 files changed, 176 insertions(+), 8 deletions(-)
 create mode 100644 src/test/java/com/thread/concurrency/CounterTest.java
 rename src/test/java/com/thread/concurrency/{ => async}/AsyncControllerTest.java (96%)
 rename src/test/java/com/thread/concurrency/{ => async}/AsyncServiceTest.java (98%)
 rename src/test/java/com/thread/concurrency/counter/{QueueCounterTest.java => BlockingQueueCounterTest.java} (95%)
 create mode 100644 src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java

diff --git a/src/test/java/com/thread/concurrency/CounterTest.java b/src/test/java/com/thread/concurrency/CounterTest.java
new file mode 100644
index 0000000..bb35d59
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/CounterTest.java
@@ -0,0 +1,57 @@
+package com.thread.concurrency;
+
+import com.thread.concurrency.counter.AtomicCounter;
+import com.thread.concurrency.counter.CompletableFutureCounter;
+import com.thread.concurrency.counter.Counter;
+import com.thread.concurrency.counter.SynchronizedCounter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Stream;
+
+@SpringBootTest
+public class CounterTest {
+    private final int valueToAdd = 1;
+    private final int nAddsPerThread = 50000000;
+    private final int nThreads = 9;
+
+    public static Stream<Counter> counterProvider() {
+        return Stream.of(new AtomicCounter(), new CompletableFutureCounter(), new SynchronizedCounter());
+    }
+
+    @ParameterizedTest
+    @MethodSource("counterProvider")
+    @DisplayName("스레드 안전한 카운터로 동시에 여러 더하기 수행하기.")
+    public void 여러_더하기_수행_Executor(Counter counter) throws InterruptedException {
+        LocalTime lt1 = LocalTime.now();
+        int initalCount = counter.show();
+
+        ExecutorService service = Executors.newFixedThreadPool(nThreads);
+        CountDownLatch latch = new CountDownLatch(nThreads);
+        for (int i = 0; i < nThreads; i++) {
+            service.submit(() -> {
+                for(int j=0; j<nAddsPerThread; j++){
+                    counter.add(valueToAdd);
+                }
+                latch.countDown();
+            });
+        }
+        latch.await();
+        int finalCount = counter.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        System.out.println("여러_더하기_수행_Executor 테스트가 걸린 시간 : " + ((float)dif / 1000000) + "ms");
+        Runtime.getRuntime().gc();
+        long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+        System.out.println("메모리 사용량 "+(double)usedMemory/1048576 + " MB");
+        Assertions.assertEquals(initalCount + nThreads*nAddsPerThread*valueToAdd, finalCount);
+    }
+}
diff --git a/src/test/java/com/thread/concurrency/AsyncControllerTest.java b/src/test/java/com/thread/concurrency/async/AsyncControllerTest.java
similarity index 96%
rename from src/test/java/com/thread/concurrency/AsyncControllerTest.java
rename to src/test/java/com/thread/concurrency/async/AsyncControllerTest.java
index 59d0f06..d501937 100644
--- a/src/test/java/com/thread/concurrency/AsyncControllerTest.java
+++ b/src/test/java/com/thread/concurrency/async/AsyncControllerTest.java
@@ -1,4 +1,4 @@
-package com.thread.concurrency;
+package com.thread.concurrency.async;
 
 import com.thread.concurrency.async.controller.AsyncController;
 import org.junit.jupiter.api.Test;
diff --git a/src/test/java/com/thread/concurrency/AsyncServiceTest.java b/src/test/java/com/thread/concurrency/async/AsyncServiceTest.java
similarity index 98%
rename from src/test/java/com/thread/concurrency/AsyncServiceTest.java
rename to src/test/java/com/thread/concurrency/async/AsyncServiceTest.java
index 90b0d2d..b4ea59c 100644
--- a/src/test/java/com/thread/concurrency/AsyncServiceTest.java
+++ b/src/test/java/com/thread/concurrency/async/AsyncServiceTest.java
@@ -1,4 +1,4 @@
-package com.thread.concurrency;
+package com.thread.concurrency.async;
 
 import com.thread.concurrency.async.service.AsyncService;
 import org.junit.jupiter.api.*;
diff --git a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java
similarity index 95%
rename from src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
rename to src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java
index 0d170b0..fdb61c9 100644
--- a/src/test/java/com/thread/concurrency/counter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java
@@ -13,9 +13,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.*;
-import java.util.function.Function;
 
-public class QueueCounterTest {
+public class BlockingQueueCounterTest {
     private final int counteNumber = 1;
     private final int totalCount = Integer.MAX_VALUE;
     private final int nThread = 15;
@@ -34,11 +33,9 @@ public class QueueCounterTest {
 
         // 프로듀서 스레드 생성
         for (int i = 0; i < nThread; i++) {
-//            int finalI = i;
             service.submit(() -> {
                 try {
                     for(int j=0; j<(totalCount/nThread); j++){
-//                        System.out.println(Thread.currentThread().getName()+"은 작업 추가 "+ finalI +" "+j);
                         producer.add(counteNumber);
                     }
                 } catch (InterruptedException e) {
diff --git a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
index 9630e2f..31137e0 100644
--- a/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
+++ b/src/test/java/com/thread/concurrency/counter/SynchronizedCounterTest.java
@@ -1,6 +1,5 @@
 package com.thread.concurrency.counter;
 
-import com.thread.concurrency.AsyncServiceTest;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -12,7 +11,6 @@
 import java.time.Duration;
 import java.time.LocalTime;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.*;
 
diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
new file mode 100644
index 0000000..89ab1d0
--- /dev/null
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -0,0 +1,116 @@
+package com.thread.concurrency.queueCounter;
+
+
+import com.thread.concurrency.counter.producerCustomer.CounterConsumer;
+import com.thread.concurrency.counter.producerCustomer.CounterProducer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.concurrent.*;
+
+public class QueueCounterTest {
+    private final int valueToAdd = 1;
+    private final int nAddsPerThread = 50000000;
+    private final int nThreads = 9;
+    private final long timeout = 1;
+    private final int queueCapacity = 1000;
+    private final TimeUnit unit = TimeUnit.SECONDS;
+
+    @Test
+    @DisplayName("멀티 프로듀서 싱글 컨슈머")
+    public void 멀티_프로듀서_싱글_컨슈머() throws InterruptedException {
+        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(queueCapacity);
+        CounterConsumer consumer = new CounterConsumer(queue);
+        CounterProducer producer = new CounterProducer(queue);
+        LocalTime lt1 = LocalTime.now();
+        Long initalCount = consumer.show();
+        ExecutorService producerService = Executors.newFixedThreadPool(nThreads);
+        ExecutorService consumerService = Executors.newFixedThreadPool(nThreads);
+        CountDownLatch producerLatch = new CountDownLatch(nThreads);
+        CountDownLatch consumerLatch = new CountDownLatch(1);
+
+        // 프로듀서 스레드 생성
+        for (int i = 0; i < nThreads; i++) {
+            producerService.submit(() -> {
+                try {
+                    for(int j=0; j<nAddsPerThread; j++){
+                        producer.add(valueToAdd);
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                producerLatch.countDown();
+            });
+        }
+
+        // 컨슈머 스레드 생성
+        consumerService.submit(() -> {
+            try {
+                consumer.consumeEvent(timeout, unit);
+                consumerLatch.countDown();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        consumerLatch.await();
+        System.out.println("컨슈머 작업 끝남");
+        producerLatch.await();
+        System.out.println("프로듀서 작업 끝남");
+
+        Long finalCount = consumer.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
+    }
+    @Test
+    @DisplayName("멀티 프로듀서 멀티 컨슈머")
+    public void 멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
+        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(queueCapacity);
+        CounterConsumer consumer = new CounterConsumer(queue);
+        CounterProducer producer = new CounterProducer(queue);
+        LocalTime lt1 = LocalTime.now();
+        Long initalCount = consumer.show();
+        ExecutorService producerService = Executors.newFixedThreadPool(nThreads);
+        ExecutorService consumerService = Executors.newFixedThreadPool(nThreads);
+        CountDownLatch producerLatch = new CountDownLatch(nThreads);
+        CountDownLatch consumerLatch = new CountDownLatch(nThreads);
+
+        // 프로듀서 스레드 생성
+        for (int i = 0; i < nThreads; i++) {
+            producerService.submit(() -> {
+                try {
+                    for(int j=0; j<nAddsPerThread; j++){
+                        producer.add(valueToAdd);
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                producerLatch.countDown();
+            });
+        }
+        // 컨슈머 스레드 생성
+        for (int i = 0; i < nThreads; i++) {
+            consumerService.submit(() -> {
+                try {
+                    consumer.consumeEvent(timeout, unit);
+                    consumerLatch.countDown();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
+        consumerLatch.await();
+        System.out.println("컨슈머 작업 끝남");
+        producerLatch.await();
+        System.out.println("프로듀서 작업 끝남");
+
+        Long finalCount = consumer.show();
+        LocalTime lt2 = LocalTime.now();
+        long dif = Duration.between(lt1, lt2).getNano();
+        System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
+    }
+}

From eb1c7117bd7e45530b90b9cb9deea969f8cdeb99 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:36:19 +0900
Subject: [PATCH 19/25] =?UTF-8?q?refacto/=EC=8A=A4=EB=A0=88=EB=93=9C=20?=
 =?UTF-8?q?=EC=88=98=20=EC=A0=95=EB=8F=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../queueCounter/QueueCounterTest.java        | 27 ++++++++++---------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
index 89ab1d0..ec66456 100644
--- a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -13,7 +13,8 @@
 public class QueueCounterTest {
     private final int valueToAdd = 1;
     private final int nAddsPerThread = 50000000;
-    private final int nThreads = 9;
+    private final int producerNThreads = 9;
+    private final int consumerNThreads = 9;
     private final long timeout = 1;
     private final int queueCapacity = 1000;
     private final TimeUnit unit = TimeUnit.SECONDS;
@@ -26,13 +27,13 @@ public class QueueCounterTest {
         CounterProducer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
         Long initalCount = consumer.show();
-        ExecutorService producerService = Executors.newFixedThreadPool(nThreads);
-        ExecutorService consumerService = Executors.newFixedThreadPool(nThreads);
-        CountDownLatch producerLatch = new CountDownLatch(nThreads);
+        ExecutorService producerService = Executors.newFixedThreadPool(producerNThreads);
+        ExecutorService consumerService = Executors.newFixedThreadPool(consumerNThreads);
+        CountDownLatch producerLatch = new CountDownLatch(producerNThreads);
         CountDownLatch consumerLatch = new CountDownLatch(1);
 
         // 프로듀서 스레드 생성
-        for (int i = 0; i < nThreads; i++) {
+        for (int i = 0; i < producerNThreads; i++) {
             producerService.submit(() -> {
                 try {
                     for(int j=0; j<nAddsPerThread; j++){
@@ -63,7 +64,7 @@ public class QueueCounterTest {
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
+        Assertions.assertEquals(initalCount + nAddsPerThread*producerNThreads*valueToAdd, finalCount);
     }
     @Test
     @DisplayName("멀티 프로듀서 멀티 컨슈머")
@@ -73,13 +74,13 @@ public class QueueCounterTest {
         CounterProducer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
         Long initalCount = consumer.show();
-        ExecutorService producerService = Executors.newFixedThreadPool(nThreads);
-        ExecutorService consumerService = Executors.newFixedThreadPool(nThreads);
-        CountDownLatch producerLatch = new CountDownLatch(nThreads);
-        CountDownLatch consumerLatch = new CountDownLatch(nThreads);
+        ExecutorService producerService = Executors.newFixedThreadPool(producerNThreads);
+        ExecutorService consumerService = Executors.newFixedThreadPool(consumerNThreads);
+        CountDownLatch producerLatch = new CountDownLatch(producerNThreads);
+        CountDownLatch consumerLatch = new CountDownLatch(consumerNThreads);
 
         // 프로듀서 스레드 생성
-        for (int i = 0; i < nThreads; i++) {
+        for (int i = 0; i < producerNThreads; i++) {
             producerService.submit(() -> {
                 try {
                     for(int j=0; j<nAddsPerThread; j++){
@@ -92,7 +93,7 @@ public class QueueCounterTest {
             });
         }
         // 컨슈머 스레드 생성
-        for (int i = 0; i < nThreads; i++) {
+        for (int i = 0; i < consumerNThreads; i++) {
             consumerService.submit(() -> {
                 try {
                     consumer.consumeEvent(timeout, unit);
@@ -111,6 +112,6 @@ public class QueueCounterTest {
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
+        Assertions.assertEquals(initalCount + nAddsPerThread*producerNThreads*valueToAdd, finalCount);
     }
 }

From 86554d681492e7d61d67437e9bd290e3e96de799 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:41:14 +0900
Subject: [PATCH 20/25] =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EC=9E=91=EC=97=85?=
 =?UTF-8?q?=EA=B3=BC=20=EB=B3=91=ED=95=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/thread/concurrency/queueCounter/QueueCounterTest.java | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
index e45ade9..4362f53 100644
--- a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -64,11 +64,7 @@ public class QueueCounterTest {
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
         System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-<<<<<<< HEAD
         Assertions.assertEquals(initalCount + nAddsPerThread*producerNThreads*valueToAdd, finalCount);
-=======
-        Assertions.assertEquals(initalCount + nAddsPerThread*nThreads*valueToAdd, finalCount);
->>>>>>> integrated_develop
     }
     @Test
     @DisplayName("멀티 프로듀서 멀티 컨슈머")

From 55a797d563deba763df580dfe9747f02a46a7202 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:46:55 +0900
Subject: [PATCH 21/25] =?UTF-8?q?sol=20=EC=BB=A4=EB=B0=8B=20=EC=A0=95?=
 =?UTF-8?q?=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 build.gradle.kts                                            | 1 -
 src/test/java/com/thread/concurrency/package-info.java      | 1 -
 .../thread/concurrency/queueCounter/QueueCounterTest.java   | 6 ++----
 3 files changed, 2 insertions(+), 6 deletions(-)
 delete mode 100644 src/test/java/com/thread/concurrency/package-info.java

diff --git a/build.gradle.kts b/build.gradle.kts
index 85502fe..dfe6a2a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,7 +18,6 @@ repositories {
 
 dependencies {
     implementation("org.springframework.boot:spring-boot-starter")
-    implementation("net.jcip:jcip-annotations:1.0")
     testImplementation("org.springframework.boot:spring-boot-starter-test")
     testRuntimeOnly("org.junit.platform:junit-platform-launcher")
     testRuntimeOnly("org.reactivestreams:reactive-streams")
diff --git a/src/test/java/com/thread/concurrency/package-info.java b/src/test/java/com/thread/concurrency/package-info.java
deleted file mode 100644
index ae29573..0000000
--- a/src/test/java/com/thread/concurrency/package-info.java
+++ /dev/null
@@ -1 +0,0 @@
-package com.thread.concurrency;
diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
index 4362f53..12a5593 100644
--- a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -63,7 +63,7 @@ public class QueueCounterTest {
         Long finalCount = consumer.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+        System.out.println("멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
         Assertions.assertEquals(initalCount + nAddsPerThread*producerNThreads*valueToAdd, finalCount);
     }
     @Test
@@ -104,14 +104,12 @@ public class QueueCounterTest {
             });
         }
         consumerLatch.await();
-        System.out.println("컨슈머 작업 끝남");
         producerLatch.await();
-        System.out.println("프로듀서 작업 끝남");
 
         Long finalCount = consumer.show();
         LocalTime lt2 = LocalTime.now();
         long dif = Duration.between(lt1, lt2).getNano();
-        System.out.println("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
+        System.out.println("멀티_프로듀서_멀티_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
         Assertions.assertEquals(initalCount + nAddsPerThread*producerNThreads*valueToAdd, finalCount);
     }
 }

From 84da0f49b92051ea71c54c54d46b42b75940db70 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:49:39 +0900
Subject: [PATCH 22/25] =?UTF-8?q?queueCounter=EB=8A=94=20counter=EC=99=80?=
 =?UTF-8?q?=20=EB=8B=A4=EB=A5=B8=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?=
 =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EA=B3=B5=EC=9C=A0=ED=95=98=EA=B8=B0=20?=
 =?UTF-8?q?=EB=95=8C=EB=AC=B8=EC=97=90=20counter=20=ED=8C=A8=ED=82=A4?=
 =?UTF-8?q?=EC=A7=80=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../counter/BlockingQueueCounterTest.java     | 121 ------------------
 1 file changed, 121 deletions(-)
 delete mode 100644 src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java

diff --git a/src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java b/src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java
deleted file mode 100644
index fdb61c9..0000000
--- a/src/test/java/com/thread/concurrency/counter/BlockingQueueCounterTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.thread.concurrency.counter;
-
-import com.thread.concurrency.counter.producerCustomer.CounterConsumer;
-import com.thread.concurrency.counter.producerCustomer.CounterProducer;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.Duration;
-import java.time.LocalTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.*;
-
-public class BlockingQueueCounterTest {
-    private final int counteNumber = 1;
-    private final int totalCount = Integer.MAX_VALUE;
-    private final int nThread = 15;
-    private static final Logger logger = LoggerFactory.getLogger(SynchronizedCounterTest.class);
-
-    @Test
-    @DisplayName("멀티 프로듀서 싱글 컨슈머")
-    public void 멀티_프로듀서_싱글_컨슈머() throws InterruptedException {
-        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
-        CounterConsumer consumer = new CounterConsumer(queue);
-        CounterProducer producer = new CounterProducer(queue);
-        LocalTime lt1 = LocalTime.now();
-        Long initalCount = consumer.show();
-        ExecutorService service = Executors.newFixedThreadPool(nThread);
-        CountDownLatch latch = new CountDownLatch(nThread);
-
-        // 프로듀서 스레드 생성
-        for (int i = 0; i < nThread; i++) {
-            service.submit(() -> {
-                try {
-                    for(int j=0; j<(totalCount/nThread); j++){
-                        producer.add(counteNumber);
-                    }
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                latch.countDown();
-            });
-        }
-        // CounterCustomer 스레드 생성 및 비동기로 처리 시작
-        CompletableFuture<Void> task = CompletableFuture.runAsync(()->{
-            try{
-                consumer.consumeEvent(10, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        });
-        try {
-            task.get();
-        } catch (ExecutionException e) {
-            throw new RuntimeException(e);
-        }
-        System.out.println("컨슈머 작업 끝남");
-        latch.await();
-        System.out.println("프로듀서 작업 끝남");
-
-        Long finalCount = consumer.show();
-        LocalTime lt2 = LocalTime.now();
-        long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-        Assertions.assertEquals(initalCount + (totalCount/nThread)*nThread*counteNumber, finalCount);
-    }
-    @Test
-    @DisplayName("멀티 프로듀서 멀티 컨슈머")
-    public void 멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
-        BlockingQueue<Long> queue = new LinkedBlockingQueue<>(1000);
-        CounterConsumer consumer = new CounterConsumer(queue);
-        CounterProducer producer = new CounterProducer(queue);
-        LocalTime lt1 = LocalTime.now();
-        Long initalCount = consumer.show();
-        ExecutorService service = Executors.newFixedThreadPool(nThread);
-        CountDownLatch latch = new CountDownLatch(nThread);
-
-        for (int i = 0; i < nThread; i++) {
-            service.submit(() -> {
-                try {
-                    for(int j=0; j<(totalCount/nThread); j++){
-                        producer.add(counteNumber);
-                    }
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                latch.countDown();
-            });
-        }
-        // CounterCustomer 스레드 생성 및 비동기로 처리 시작
-        List<CompletableFuture<Void>> tasks = new ArrayList<>();
-        for(int i=0; i<3; i++){
-            tasks.add( CompletableFuture.runAsync(()->{
-                try{
-                    consumer.consumeEvent(1, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }));
-        }
-        tasks.forEach((t) -> {
-            try {
-//                System.out.println("적당히 돌아가는거 확인합닛다.");
-                t.get();
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        });
-        System.out.println("컨슈머 작업 끝남");
-        latch.await();
-        System.out.println("프로듀서 작업 끝남");
-        Long finalCount = consumer.show();
-        LocalTime lt2 = LocalTime.now();
-        long dif = Duration.between(lt1, lt2).getNano();
-        logger.info("프로듀서_컨슈며_더하기_멀티_프로듀서_단일_컨슈머 테스트가 걸린 시간 : " + dif / 1000000 + "ms");
-        Assertions.assertEquals(initalCount + (totalCount/nThread)*nThread*counteNumber, finalCount);
-    }
-}

From e783deb54156277e5dbfa08c4e3fadecd42155e9 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 17:55:06 +0900
Subject: [PATCH 23/25] =?UTF-8?q?=F0=9F=A4=96=20Refactor=20:=20=EC=BD=94?=
 =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=E2=9C=85=20?=
 =?UTF-8?q?Test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1. 프로듀서 컨슈머의 인터페이스 작성
2. 인터페이스를 이용해 테스트 리팩토링
---
 .../producerCustomer/CounterConsumer.java     | 39 -------------------
 .../producerCustomer/CounterProducer.java     | 16 --------
 .../counter/queueCounter/Consumer.java        |  8 ++++
 .../counter/queueCounter/CounterConsumer.java |  5 +--
 .../counter/queueCounter/CounterProducer.java |  2 +-
 .../counter/queueCounter/Producer.java        |  5 +++
 .../queueCounter/QueueCounterTest.java        | 10 +++--
 7 files changed, 21 insertions(+), 64 deletions(-)
 delete mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
 delete mode 100644 src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/queueCounter/Consumer.java
 create mode 100644 src/main/java/com/thread/concurrency/counter/queueCounter/Producer.java

diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
deleted file mode 100644
index 61f7c5c..0000000
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterConsumer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.thread.concurrency.counter.producerCustomer;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Function;
-import java.util.function.IntUnaryOperator;
-import java.util.function.LongUnaryOperator;
-
-public class CounterConsumer {
-    private final BlockingQueue<Long> queue;
-    private final AtomicLong count = new AtomicLong(0);
-
-    public CounterConsumer(BlockingQueue<Long> queue) {
-        this.queue = queue;
-    }
-
-    public void consumeEvent(long timeout, TimeUnit unit) throws InterruptedException {
-        while (true) {
-//            System.out.println(Thread.currentThread().getName()+"은 현재 큐 사이즈 : "+queue.size());
-            Long value = queue.poll(timeout, unit);
-            if(value == null){
-//                System.out.println(Thread.currentThread().getName()+" 끝났으!!");
-                break;
-            }
-            count.addAndGet(value);
-//            System.out.println("결s과 카운트 : "+count.addAndGet(value));
-        }
-    }
-    public Long show(){
-        while(true){
-            if(queue.isEmpty()){
-                return count.get();
-            }
-        }
-    }
-}
diff --git a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
deleted file mode 100644
index 74df1d7..0000000
--- a/src/main/java/com/thread/concurrency/counter/producerCustomer/CounterProducer.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.thread.concurrency.counter.producerCustomer;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.function.Function;
-
-public class CounterProducer {
-    private final BlockingQueue<Long> queue;
-
-    public CounterProducer(BlockingQueue<Long> queue) {
-        this.queue = queue;
-    }
-
-    public void add(long value) throws InterruptedException {
-        queue.put(value);
-    }
-}
diff --git a/src/main/java/com/thread/concurrency/counter/queueCounter/Consumer.java b/src/main/java/com/thread/concurrency/counter/queueCounter/Consumer.java
new file mode 100644
index 0000000..11930df
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/queueCounter/Consumer.java
@@ -0,0 +1,8 @@
+package com.thread.concurrency.counter.queueCounter;
+
+import java.util.concurrent.TimeUnit;
+
+public interface Consumer {
+    void consumeEvent(long timeout, TimeUnit unit) throws InterruptedException;
+    Long show();
+}
diff --git a/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java
index c12edf1..964d86c 100644
--- a/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java
+++ b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterConsumer.java
@@ -4,7 +4,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
-public class CounterConsumer {
+public class CounterConsumer implements Consumer {
     private final BlockingQueue<Long> queue;
     private final AtomicLong count = new AtomicLong(0);
 
@@ -14,14 +14,11 @@ public CounterConsumer(BlockingQueue<Long> queue) {
 
     public void consumeEvent(long timeout, TimeUnit unit) throws InterruptedException {
         while (true) {
-//            System.out.println(Thread.currentThread().getName()+"은 현재 큐 사이즈 : "+queue.size());
             Long value = queue.poll(timeout, unit);
             if(value == null){
-//                System.out.println(Thread.currentThread().getName()+" 끝났으!!");
                 break;
             }
             count.addAndGet(value);
-//            System.out.println("결s과 카운트 : "+count.addAndGet(value));
         }
     }
     public Long show(){
diff --git a/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java
index 126e608..24afef8 100644
--- a/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java
+++ b/src/main/java/com/thread/concurrency/counter/queueCounter/CounterProducer.java
@@ -2,7 +2,7 @@
 
 import java.util.concurrent.BlockingQueue;
 
-public class CounterProducer {
+public class CounterProducer implements Producer{
     private final BlockingQueue<Long> queue;
 
     public CounterProducer(BlockingQueue<Long> queue) {
diff --git a/src/main/java/com/thread/concurrency/counter/queueCounter/Producer.java b/src/main/java/com/thread/concurrency/counter/queueCounter/Producer.java
new file mode 100644
index 0000000..71d943d
--- /dev/null
+++ b/src/main/java/com/thread/concurrency/counter/queueCounter/Producer.java
@@ -0,0 +1,5 @@
+package com.thread.concurrency.counter.queueCounter;
+
+public interface Producer {
+    void add(long value) throws InterruptedException;
+}
diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
index 12a5593..8180d7d 100644
--- a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -1,7 +1,9 @@
 package com.thread.concurrency.queueCounter;
 
+import com.thread.concurrency.counter.queueCounter.Consumer;
 import com.thread.concurrency.counter.queueCounter.CounterConsumer;
 import com.thread.concurrency.counter.queueCounter.CounterProducer;
+import com.thread.concurrency.counter.queueCounter.Producer;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -23,8 +25,8 @@ public class QueueCounterTest {
     @DisplayName("멀티 프로듀서 싱글 컨슈머")
     public void 멀티_프로듀서_싱글_컨슈머() throws InterruptedException {
         BlockingQueue<Long> queue = new LinkedBlockingQueue<>(queueCapacity);
-        CounterConsumer consumer = new CounterConsumer(queue);
-        CounterProducer producer = new CounterProducer(queue);
+        Consumer consumer = new CounterConsumer(queue);
+        Producer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
         Long initalCount = consumer.show();
         ExecutorService producerService = Executors.newFixedThreadPool(producerNThreads);
@@ -70,8 +72,8 @@ public class QueueCounterTest {
     @DisplayName("멀티 프로듀서 멀티 컨슈머")
     public void 멀티_프로듀서_멀티_컨슈머() throws InterruptedException {
         BlockingQueue<Long> queue = new LinkedBlockingQueue<>(queueCapacity);
-        CounterConsumer consumer = new CounterConsumer(queue);
-        CounterProducer producer = new CounterProducer(queue);
+        Consumer consumer = new CounterConsumer(queue);
+        Producer producer = new CounterProducer(queue);
         LocalTime lt1 = LocalTime.now();
         Long initalCount = consumer.show();
         ExecutorService producerService = Executors.newFixedThreadPool(producerNThreads);

From 75b0f90dbeec99f17d2f0cbe9fde5e4f5f182652 Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 18:01:26 +0900
Subject: [PATCH 24/25] =?UTF-8?q?=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20?=
 =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EA=B5=AC=ED=98=84=EC=A0=9C=20=EC=A0=9C?=
 =?UTF-8?q?=EA=B1=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../concurrency/counter/BasicCounterTest.java | 35 -------------------
 1 file changed, 35 deletions(-)
 delete mode 100644 src/test/java/com/thread/concurrency/counter/BasicCounterTest.java

diff --git a/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java b/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
deleted file mode 100644
index ffc7e7f..0000000
--- a/src/test/java/com/thread/concurrency/counter/BasicCounterTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.thread.concurrency.counter;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import java.util.concurrent.CompletableFuture;
-
-@SpringBootTest
-public class BasicCounterTest {
-
-    private final BasicCounter basicCounter;
-    private final int counteNumber = 1;
-    private final int totalCount = 100;
-
-    @Autowired
-    public BasicCounterTest(BasicCounter basicCounter) {
-        this.basicCounter = basicCounter;
-    }
-
-    @Test
-    @DisplayName("스레드 안전하지 않는 카운터로 동시에 여러 더하기 수행하기. 실패 예상")
-    public void 여러_더하기_수행(){
-        int initalCount = basicCounter.show();
-
-       for(int i=0; i<totalCount; i++){
-           CompletableFuture.runAsync(() -> {
-               basicCounter.add(counteNumber);
-           });
-       }
-        int finalCount = basicCounter.show();
-        Assertions.assertNotEquals(initalCount+totalCount*counteNumber, finalCount);
-    }
-}

From e962db2cd0b3652bb232c2a9dde9f54c15765dbf Mon Sep 17 00:00:00 2001
From: ohchansol <haxr369@gmail.com>
Date: Sun, 7 Apr 2024 18:05:08 +0900
Subject: [PATCH 25/25] =?UTF-8?q?=ED=95=84=EC=9A=94=20=EC=97=86=EB=8A=94?=
 =?UTF-8?q?=20=EC=B6=9C=EB=A0=A5=20=EC=A0=9C=EA=B1=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/thread/concurrency/queueCounter/QueueCounterTest.java   | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
index 8180d7d..56eb89d 100644
--- a/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
+++ b/src/test/java/com/thread/concurrency/queueCounter/QueueCounterTest.java
@@ -58,9 +58,7 @@ public class QueueCounterTest {
             }
         });
         consumerLatch.await();
-        System.out.println("컨슈머 작업 끝남");
         producerLatch.await();
-        System.out.println("프로듀서 작업 끝남");
 
         Long finalCount = consumer.show();
         LocalTime lt2 = LocalTime.now();