Skip to content

Commit

Permalink
Counter 예제 구현 (#31)
Browse files Browse the repository at this point in the history
## 개요

- 다양한 카운터 구현체

## 변경 사항

- [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
  • Loading branch information
ooMia authored Apr 11, 2024
2 parents 7bf0d66 + 1fe90f4 commit e462d92
Show file tree
Hide file tree
Showing 29 changed files with 868 additions and 24 deletions.
17 changes: 10 additions & 7 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
Expand All @@ -24,15 +25,17 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]

- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]

**Additional context**
Add any other context about the problem here.
3 changes: 2 additions & 1 deletion .github/workflows/gradle-test-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: spring-templates/spring-concurrency-thread
fail_ci_if_error: true
flags: integration
fail_ci_if_error: true
verbose: true
11 changes: 6 additions & 5 deletions .github/workflows/gradle-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ name: Run gradlew test

on:
pull_request:
branches-ignore:
- main
branches:
- develop
- feature/**

jobs:
build:
Expand Down Expand Up @@ -36,6 +37,6 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: spring-templates/spring-concurrency-thread
fail_ci_if_error: true
verbose: true
flags: unittests
fail_ci_if_error: true
verbose: true
flags: ${{ github.ref == 'refs/pull/develop' && 'integration' || 'unittests' }}
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
HELP.md
.gradle
build/
.gradle/*
build/*
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
Expand All @@ -18,7 +18,7 @@ bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
.idea/*
*.iws
*.iml
*.ipr
Expand Down
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
# spring-thread-concurrency
[![codecov](https://codecov.io/gh/spring-templates/spring-concurrency-thread/graph/badge.svg?token=N3GEH8C5K7)](https://codecov.io/gh/spring-templates/spring-concurrency-thread)

![Spring Boot](https://img.shields.io/badge/Spring%20Boot-6DB33F?logo=springboot&logoColor=white)
![Java](https://img.shields.io/badge/Java-ED8B00?logoColor=white)
![Gradle](https://img.shields.io/badge/Gradle-02303A?logo=gradle&logoColor=white)
![JUnit5](https://img.shields.io/badge/JUnit5-25A162?logo=junit5&logoColor=white)
![JaCoCo](https://img.shields.io/badge/JaCoCo-D22128?logo=jacoco&logoColor=white)
![Codecov](https://img.shields.io/badge/Codecov-F01F7A?logo=codecov&logoColor=white)
![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-2088FF?logo=githubactions&logoColor=white)

# 관심사

- [멀티 스레드의 자원 공유 문제](https://github.com/spring-templates/spring-concurrency-thread/discussions/16)
- [멀티 쓰레드 자원 업데이트 문제](https://github.com/spring-templates/spring-concurrency-thread/discussions/17)

# 정보

- [동시성 기본 조건과 관심사](https://github.com/spring-templates/spring-concurrency-thread/discussions/2)

# [Counter-implementation Benchmark](https://www.notion.so/softsquared/f314375356b54381a8878cf2dabd381b)

> - median of 25 iterations
> - nRequests: 2^21 - 1
| name | nThreads | time (ms) | memory (KB) |
|-------------------|----------|-----------|-------------|
| AtomicBatch | 4 | 12 | 480 |
| Atomic | 1 | 14 | 318 |
| AtomicBatch | 1 | 30 | 240 |
| Lock | 1 | 61 | 241 |
| Synchronized | 1 | 61 | 241 |
| Polling | 1 | 78 | 463 |
| CompletableFuture | 1 | 158 | 25710 |

### AtomicBatch vs Atomic

> - nThreads: AtomicBatch=4, Atomic=1
| name | nRequests | time (ms) | memory (KB) |
|-------------|-----------|-----------|-------------|
| AtomicBatch | 2^21 - 1 | 12 | 480 |
| AtomicBatch | 2^22 - 1 | 24 | 538 |
| AtomicBatch | 2^23 - 1 | 42 | 572 |
| AtomicBatch | 2^30 - 1 | 5695 | 511 |
| AtomicBatch | 2^31 - 1 | 11621 | 294 |
| Atomic | 2^21 - 1 | 14 | 318 |
| Atomic | 2^22 - 1 | 27 | 244 |
| Atomic | 2^23 - 1 | 55 | 344 |
| Atomic | 2^30 - 1 | 7178 | 103 |
| Atomic | 2^31 - 1 | 14377 | 266 |
10 changes: 10 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
coverage:
status:
project:
default:
target: 40%
threshold: 10%
patch:
default:
target: 30%
threshold: 10%
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.thread.concurrency;

import com.thread.concurrency.executor.CounterBenchmark;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringThreadConcurrencyApplication {

public static void main(String[] args) {
SpringApplication.run(SpringThreadConcurrencyApplication.class, args);
ConfigurableApplicationContext context = SpringApplication.run(SpringThreadConcurrencyApplication.class, args);
var performance = context.getBean(CounterBenchmark.class).benchmark();
System.out.println("|----------------------|---------------|---------------|---------------|");
System.out.println(performance);
}

}
25 changes: 25 additions & 0 deletions src/main/java/com/thread/concurrency/counter/AtomicCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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();
}

@Override
public void clear() {
count.set(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.thread.concurrency.counter;

import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@Component
public class CompletableFutureCounter implements Counter {
private CompletableFuture<Integer> counter;

public CompletableFutureCounter() {
this.counter = new CompletableFuture<>();
counter.complete(100);
}

@Override
public void add(int value) {
synchronized (this) {
counter = counter.thenApply((c) -> c + value);
}
}

@Override
public int show() {
try {
return counter.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}

@Override
public void clear() {
synchronized (this) {
counter = CompletableFuture.completedFuture(0);
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/thread/concurrency/counter/Counter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.thread.concurrency.counter;

public interface Counter {
void add(int value);

int show();

void clear();
}
36 changes: 36 additions & 0 deletions src/main/java/com/thread/concurrency/counter/LockCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.thread.concurrency.counter;

import org.springframework.stereotype.Component;

import java.util.concurrent.locks.ReentrantLock;

@Component
public class LockCounter implements Counter {
private static final ReentrantLock lock = new ReentrantLock();
private static int count = 100;

@Override
public void add(int value) {
lock.lock();
try {
count += value;
} finally {
lock.unlock();
}
}

@Override
public int show() {
return count;
}

@Override
public void clear() {
lock.lock();
try {
count = 0;
} finally {
lock.unlock();
}
}
}
42 changes: 42 additions & 0 deletions src/main/java/com/thread/concurrency/counter/PollingCounter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.thread.concurrency.counter;

import org.springframework.stereotype.Component;

@Component
public class PollingCounter implements Counter {
private static int count = 100;
private static volatile boolean lock = false;

@Override
public void add(int value) {
while (true) {
if (!lock) {
synchronized (PollingCounter.class) {
lock = true;
count += value;
lock = false;
break;
}
}
}
}

@Override
public int show() {
return count;
}

@Override
public void clear() {
while (true) {
if (!lock) {
synchronized (PollingCounter.class) {
lock = true;
count = 0;
lock = false;
break;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.thread.concurrency.counter;

import org.springframework.stereotype.Component;

@Component
public class SynchronizedCounter implements Counter {

private int counter = 100;

@Override
public synchronized void add(int value) {
counter += value;
}

@Override
public synchronized int show() {
return counter;
}

@Override
public synchronized void clear() {
counter = 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.thread.concurrency.counter.batch;

import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

@Component
public class AtomicBatchCounter implements BatchCounter {
private final AtomicLong counter = new AtomicLong();
private final ConcurrentMap<Long, LongAdder> batch = new ConcurrentHashMap<>();

@Override
public void add(int value) {
var threadId = Thread.currentThread().threadId();
batch.computeIfAbsent(threadId, k -> new LongAdder()).add(value);
}

@Override
public int show() {
return counter.intValue();
}

@Override
public void clear() {
counter.set(0);
batch.clear();
}

@Override
public void flush() {
var threadId = Thread.currentThread().threadId();
flush(threadId);
}

private void flush(long threadId) {
var value = batch.remove(threadId);
if (value != null) {
counter.addAndGet(value.longValue());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.thread.concurrency.counter.batch;

import com.thread.concurrency.counter.Counter;

public interface BatchCounter extends Counter {
void flush();
}
Loading

0 comments on commit e462d92

Please sign in to comment.