Skip to content

Commit e462d92

Browse files
authored
Counter 예제 구현 (#31)
## 개요 - 다양한 카운터 구현체 ## 변경 사항 - [x] ✨ Feat : AtomicInteger 카운터 - [x] ✨ Feat : CompletableFuture 카운터 - [x] ✨ Feat : Synchronized 카운터 - [x] ✨ Feat : `BasicCounter` - [x] ✨ Feat : `LockCounter` - [x] ✨ Feat : `PollingCounter` - [x] ✨ Feat : `BatchingCounter` ## 추가 정보 ### 관련 이슈 Close #17 -> Close #27
2 parents 7bf0d66 + 1fe90f4 commit e462d92

29 files changed

+868
-24
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A clear and concise description of what the bug is.
1212

1313
**To Reproduce**
1414
Steps to reproduce the behavior:
15+
1516
1. Go to '...'
1617
2. Click on '....'
1718
3. Scroll down to '....'
@@ -24,15 +25,17 @@ A clear and concise description of what you expected to happen.
2425
If applicable, add screenshots to help explain your problem.
2526

2627
**Desktop (please complete the following information):**
27-
- OS: [e.g. iOS]
28-
- Browser [e.g. chrome, safari]
29-
- Version [e.g. 22]
28+
29+
- OS: [e.g. iOS]
30+
- Browser [e.g. chrome, safari]
31+
- Version [e.g. 22]
3032

3133
**Smartphone (please complete the following information):**
32-
- Device: [e.g. iPhone6]
33-
- OS: [e.g. iOS8.1]
34-
- Browser [e.g. stock browser, safari]
35-
- Version [e.g. 22]
34+
35+
- Device: [e.g. iPhone6]
36+
- OS: [e.g. iOS8.1]
37+
- Browser [e.g. stock browser, safari]
38+
- Version [e.g. 22]
3639

3740
**Additional context**
3841
Add any other context about the problem here.

.github/workflows/gradle-test-main.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ jobs:
2828
with:
2929
token: ${{ secrets.CODECOV_TOKEN }}
3030
slug: spring-templates/spring-concurrency-thread
31-
fail_ci_if_error: true
31+
flags: integration
32+
fail_ci_if_error: true
3233
verbose: true

.github/workflows/gradle-test.yml

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ name: Run gradlew test
22

33
on:
44
pull_request:
5-
branches-ignore:
6-
- main
5+
branches:
6+
- develop
7+
- feature/**
78

89
jobs:
910
build:
@@ -36,6 +37,6 @@ jobs:
3637
with:
3738
token: ${{ secrets.CODECOV_TOKEN }}
3839
slug: spring-templates/spring-concurrency-thread
39-
fail_ci_if_error: true
40-
verbose: true
41-
flags: unittests
40+
fail_ci_if_error: true
41+
verbose: true
42+
flags: ${{ github.ref == 'refs/pull/develop' && 'integration' || 'unittests' }}

.gitignore

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
HELP.md
2-
.gradle
3-
build/
2+
.gradle/*
3+
build/*
44
!gradle/wrapper/gradle-wrapper.jar
55
!**/src/main/**/build/
66
!**/src/test/**/build/
@@ -18,7 +18,7 @@ bin/
1818
!**/src/test/**/bin/
1919

2020
### IntelliJ IDEA ###
21-
.idea
21+
.idea/*
2222
*.iws
2323
*.iml
2424
*.ipr

README.md

+50-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,50 @@
1-
# spring-thread-concurrency
1+
[![codecov](https://codecov.io/gh/spring-templates/spring-concurrency-thread/graph/badge.svg?token=N3GEH8C5K7)](https://codecov.io/gh/spring-templates/spring-concurrency-thread)
2+
3+
![Spring Boot](https://img.shields.io/badge/Spring%20Boot-6DB33F?logo=springboot&logoColor=white)
4+
![Java](https://img.shields.io/badge/Java-ED8B00?logoColor=white)
5+
![Gradle](https://img.shields.io/badge/Gradle-02303A?logo=gradle&logoColor=white)
6+
![JUnit5](https://img.shields.io/badge/JUnit5-25A162?logo=junit5&logoColor=white)
7+
![JaCoCo](https://img.shields.io/badge/JaCoCo-D22128?logo=jacoco&logoColor=white)
8+
![Codecov](https://img.shields.io/badge/Codecov-F01F7A?logo=codecov&logoColor=white)
9+
![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-2088FF?logo=githubactions&logoColor=white)
10+
11+
# 관심사
12+
13+
- [멀티 스레드의 자원 공유 문제](https://github.com/spring-templates/spring-concurrency-thread/discussions/16)
14+
- [멀티 쓰레드 자원 업데이트 문제](https://github.com/spring-templates/spring-concurrency-thread/discussions/17)
15+
16+
# 정보
17+
18+
- [동시성 기본 조건과 관심사](https://github.com/spring-templates/spring-concurrency-thread/discussions/2)
19+
20+
# [Counter-implementation Benchmark](https://www.notion.so/softsquared/f314375356b54381a8878cf2dabd381b)
21+
22+
> - median of 25 iterations
23+
> - nRequests: 2^21 - 1
24+
25+
| name | nThreads | time (ms) | memory (KB) |
26+
|-------------------|----------|-----------|-------------|
27+
| AtomicBatch | 4 | 12 | 480 |
28+
| Atomic | 1 | 14 | 318 |
29+
| AtomicBatch | 1 | 30 | 240 |
30+
| Lock | 1 | 61 | 241 |
31+
| Synchronized | 1 | 61 | 241 |
32+
| Polling | 1 | 78 | 463 |
33+
| CompletableFuture | 1 | 158 | 25710 |
34+
35+
### AtomicBatch vs Atomic
36+
37+
> - nThreads: AtomicBatch=4, Atomic=1
38+
39+
| name | nRequests | time (ms) | memory (KB) |
40+
|-------------|-----------|-----------|-------------|
41+
| AtomicBatch | 2^21 - 1 | 12 | 480 |
42+
| AtomicBatch | 2^22 - 1 | 24 | 538 |
43+
| AtomicBatch | 2^23 - 1 | 42 | 572 |
44+
| AtomicBatch | 2^30 - 1 | 5695 | 511 |
45+
| AtomicBatch | 2^31 - 1 | 11621 | 294 |
46+
| Atomic | 2^21 - 1 | 14 | 318 |
47+
| Atomic | 2^22 - 1 | 27 | 244 |
48+
| Atomic | 2^23 - 1 | 55 | 344 |
49+
| Atomic | 2^30 - 1 | 7178 | 103 |
50+
| Atomic | 2^31 - 1 | 14377 | 266 |

codecov.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: 40%
6+
threshold: 10%
7+
patch:
8+
default:
9+
target: 30%
10+
threshold: 10%
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.thread.concurrency;
22

3+
import com.thread.concurrency.executor.CounterBenchmark;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.context.ConfigurableApplicationContext;
57

68
@SpringBootApplication
79
public class SpringThreadConcurrencyApplication {
810

911
public static void main(String[] args) {
10-
SpringApplication.run(SpringThreadConcurrencyApplication.class, args);
12+
ConfigurableApplicationContext context = SpringApplication.run(SpringThreadConcurrencyApplication.class, args);
13+
var performance = context.getBean(CounterBenchmark.class).benchmark();
14+
System.out.println("|----------------------|---------------|---------------|---------------|");
15+
System.out.println(performance);
1116
}
12-
1317
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.thread.concurrency.counter;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.util.concurrent.atomic.AtomicInteger;
6+
7+
@Component
8+
public class AtomicCounter implements Counter {
9+
private final AtomicInteger count = new AtomicInteger(100);
10+
11+
@Override
12+
public void add(int value) {
13+
count.addAndGet(value);
14+
}
15+
16+
@Override
17+
public int show() {
18+
return count.get();
19+
}
20+
21+
@Override
22+
public void clear() {
23+
count.set(0);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.thread.concurrency.counter;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.util.concurrent.CompletableFuture;
6+
import java.util.concurrent.ExecutionException;
7+
8+
@Component
9+
public class CompletableFutureCounter implements Counter {
10+
private CompletableFuture<Integer> counter;
11+
12+
public CompletableFutureCounter() {
13+
this.counter = new CompletableFuture<>();
14+
counter.complete(100);
15+
}
16+
17+
@Override
18+
public void add(int value) {
19+
synchronized (this) {
20+
counter = counter.thenApply((c) -> c + value);
21+
}
22+
}
23+
24+
@Override
25+
public int show() {
26+
try {
27+
return counter.get();
28+
} catch (InterruptedException | ExecutionException e) {
29+
throw new RuntimeException(e);
30+
}
31+
}
32+
33+
@Override
34+
public void clear() {
35+
synchronized (this) {
36+
counter = CompletableFuture.completedFuture(0);
37+
}
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.thread.concurrency.counter;
2+
3+
public interface Counter {
4+
void add(int value);
5+
6+
int show();
7+
8+
void clear();
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.thread.concurrency.counter;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.util.concurrent.locks.ReentrantLock;
6+
7+
@Component
8+
public class LockCounter implements Counter {
9+
private static final ReentrantLock lock = new ReentrantLock();
10+
private static int count = 100;
11+
12+
@Override
13+
public void add(int value) {
14+
lock.lock();
15+
try {
16+
count += value;
17+
} finally {
18+
lock.unlock();
19+
}
20+
}
21+
22+
@Override
23+
public int show() {
24+
return count;
25+
}
26+
27+
@Override
28+
public void clear() {
29+
lock.lock();
30+
try {
31+
count = 0;
32+
} finally {
33+
lock.unlock();
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.thread.concurrency.counter;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
@Component
6+
public class PollingCounter implements Counter {
7+
private static int count = 100;
8+
private static volatile boolean lock = false;
9+
10+
@Override
11+
public void add(int value) {
12+
while (true) {
13+
if (!lock) {
14+
synchronized (PollingCounter.class) {
15+
lock = true;
16+
count += value;
17+
lock = false;
18+
break;
19+
}
20+
}
21+
}
22+
}
23+
24+
@Override
25+
public int show() {
26+
return count;
27+
}
28+
29+
@Override
30+
public void clear() {
31+
while (true) {
32+
if (!lock) {
33+
synchronized (PollingCounter.class) {
34+
lock = true;
35+
count = 0;
36+
lock = false;
37+
break;
38+
}
39+
}
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.thread.concurrency.counter;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
@Component
6+
public class SynchronizedCounter implements Counter {
7+
8+
private int counter = 100;
9+
10+
@Override
11+
public synchronized void add(int value) {
12+
counter += value;
13+
}
14+
15+
@Override
16+
public synchronized int show() {
17+
return counter;
18+
}
19+
20+
@Override
21+
public synchronized void clear() {
22+
counter = 0;
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.thread.concurrency.counter.batch;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.util.concurrent.ConcurrentHashMap;
6+
import java.util.concurrent.ConcurrentMap;
7+
import java.util.concurrent.atomic.AtomicLong;
8+
import java.util.concurrent.atomic.LongAdder;
9+
10+
@Component
11+
public class AtomicBatchCounter implements BatchCounter {
12+
private final AtomicLong counter = new AtomicLong();
13+
private final ConcurrentMap<Long, LongAdder> batch = new ConcurrentHashMap<>();
14+
15+
@Override
16+
public void add(int value) {
17+
var threadId = Thread.currentThread().threadId();
18+
batch.computeIfAbsent(threadId, k -> new LongAdder()).add(value);
19+
}
20+
21+
@Override
22+
public int show() {
23+
return counter.intValue();
24+
}
25+
26+
@Override
27+
public void clear() {
28+
counter.set(0);
29+
batch.clear();
30+
}
31+
32+
@Override
33+
public void flush() {
34+
var threadId = Thread.currentThread().threadId();
35+
flush(threadId);
36+
}
37+
38+
private void flush(long threadId) {
39+
var value = batch.remove(threadId);
40+
if (value != null) {
41+
counter.addAndGet(value.longValue());
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.thread.concurrency.counter.batch;
2+
3+
import com.thread.concurrency.counter.Counter;
4+
5+
public interface BatchCounter extends Counter {
6+
void flush();
7+
}

0 commit comments

Comments
 (0)