Skip to content

Commit 0170514

Browse files
authored
[week5][희동] - 5주차 (#32)
1 parent 459ce82 commit 0170514

File tree

2 files changed

+175
-1
lines changed

2 files changed

+175
-1
lines changed

Diff for: heedong/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
| 2주차 |[volatile](./volatile.md)<br>• [synchronized](./synchronized.md)<br>• [concurrent.Lock](./concurrentLock.md) |
66
| 3주차 |[생산 소비자 문제](bounded-buffer.md) |
77
| 4주차 |[CAS(Compare-And-Swap)](cas.md)<br>• [동시성 컬렉션](concurrent-collection.md) |
8-
| 5주차 | |
8+
| 5주차 | [Executor 프레임워크](executor.md) |

Diff for: heedong/executor.md

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# 스레드 풀과 Executor 프레임워크
2+
3+
## 스레드를 직접 사용할 때의 문제점
4+
1. 스레드 생성 시간으로 인한 성능 문제
5+
- 메모리 할당 (스레드 별 스택 메모리)
6+
- OS 자원/스케줄러 활용 (시스템 콜 및 스케줄링에 따른 오버헤드)
7+
2. 스레드 관리 문제
8+
- 시스템이 유지될 수 있는 최대 스레드의 수 까지만 제어해야 함
9+
3. `Runnable` 인터페이스의 불편함
10+
- 반환 값이 없고, 예외 처리가 불편함
11+
12+
<br>
13+
14+
## Executor 프레임워크
15+
자바의 Executor 프레임워크는 멀티스레딩 및 병렬 처리를 쉽게 사용할 수 있도록 돕는 기능의 모음
16+
17+
`ExecutorService` 인터페이스의 기본 구현체는 `ThreadPoolExecutor`
18+
19+
```java
20+
class ExecutorTest {
21+
22+
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
23+
24+
void doSomething() {
25+
executor.submit(() -> {
26+
// task
27+
});
28+
}
29+
}
30+
```
31+
32+
`ExecutorService``execute()` 메서드와 `submit()` 메서드 모두 호출이 가능
33+
- `execute()` 메서드는 `Executor` 인터페이스의 구현
34+
- `submit()` 메서드는 `ExecutorService` 인터페이스의 구현
35+
36+
차이점은 `execute()` 메서드는 `Runnable` 인터페이스를 구현한 객체만 인자로 받아 스레드 풀에 작업을 넣어줌
37+
38+
`submit()` 메서드는 `Runnable` 또는 `Callable` 인터페이스를 구현한 객체도 인자로 받아 스레드 풀에 작업을 넣어줌
39+
40+
또한 `submit()` 메서드는 `Future` 객체를 반환해줌 (`execute()` 메서드는 리턴 값을 반환하지 않음)
41+
- `Future` 객체는 작업의 상태를 확인하거나 작업의 결과를 가져올 수 있음
42+
43+
<br>
44+
45+
### Runnable vs Callable
46+
`Runnable` 인터페이스는 `void` 타입의 `run()` 메서드를 가지고 있음
47+
```java
48+
package java.lang;
49+
50+
@FunctionalInterface
51+
public interface Runnable {
52+
void run();
53+
}
54+
```
55+
56+
`Callable` 인터페이스는 제네릭 타입 설정을 통해 리턴 값을 반환하는 `call()` 메서드를 가지고 있음
57+
58+
```java
59+
package java.util.concurrent;
60+
61+
@FunctionalInterface
62+
public interface Callable<V> {
63+
V call() throws Exception;
64+
}
65+
66+
```
67+
68+
<br>
69+
70+
### Future
71+
`Future` 는 작업의 미래 결과를 받을 수 있는 객체
72+
- 비동기적인 작업의 결과를 나타내는 인터페이스
73+
- `Future` 객체를 통해 작입이 진해될 때 요청 스레드는 대기하지 않고, 다른 작업을 수행할 수 있음
74+
75+
```java
76+
class ExecutorTest {
77+
78+
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
79+
80+
void doSomething() {
81+
var future = executor.submit(() -> {
82+
// task
83+
});
84+
85+
val result = future.get();
86+
}
87+
}
88+
```
89+
90+
`future.get()` 을 호출했을 때
91+
- **Future가 완료 상태**: `Future` 가 완료 상태면 `Future` 에 결과도 포함되어 있음
92+
- 이 경우 요청 스레드는 대기하지 않고, 값을 즉시 반환받을 수 있음
93+
- **Future가 완료 상태가 아님**: `task` 가 아직 수행되지 않았거나 또는 수행 중
94+
- 이 때는 어쩔 수 없이 요청 스레드가 결과를 받기 위해 대기해야 함
95+
- 요청 스레드가 마치 락을 얻을 때처럼 결과를 얻기 위해 대기
96+
97+
<br>
98+
99+
### 종료 메서드
100+
`void shutdown()`
101+
- 새로운 작업을 받지 않고, 이미 제출된 작업을 모두 완료한 후에 종료
102+
- 논 블로킹 동작 (이 메서드를 호출한 스레드는 대기하지 않고 즉시 다음 코드를 호출)
103+
104+
105+
`List<Runnable> shutdownNow()`
106+
- 실행 중인 작업을 중단하고, 대기 중인 작업을 반환하며 즉시 종료
107+
- 실행 중인 작업을 중단하기 위해 인터럽트를 발생
108+
- 논 블로킹 동작
109+
110+
111+
`close()`
112+
- 자바 19부터 지원하는 서비즈 종료 메서드
113+
- `shutdown()` 을 호출하고, 하루(1일)를 기다려도 작업이 완료되지 않으면 `shutdownNow()` 를 호출
114+
- 호출한 스레드에 인터럽트가 발생해도 `shutdownNow()` 를 호출한다.
115+
116+
```java
117+
default void close() {
118+
boolean terminated = isTerminated();
119+
if (!terminated) {
120+
shutdown();
121+
boolean interrupted = false;
122+
while (!terminated) {
123+
try {
124+
terminated = awaitTermination(1L, TimeUnit.DAYS);
125+
} catch (InterruptedException e) {
126+
if (!interrupted) {
127+
shutdownNow();
128+
interrupted = true;
129+
}
130+
}
131+
}
132+
if (interrupted) {
133+
Thread.currentThread().interrupt();
134+
}
135+
}
136+
}
137+
```
138+
139+
<br>
140+
141+
## Executor 스레드 풀 관리
142+
### 속성
143+
```java
144+
public ThreadPoolExecutor(
145+
int corePoolSize,
146+
int maximumPoolSize,
147+
long keepAliveTime,
148+
TimeUnit unit,
149+
BlockingQueue<Runnable> workQueue
150+
) {
151+
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
152+
}
153+
```
154+
155+
`ExecutorService` 의 기본 구현체인 `ThreadPoolExecutor` 의 생성자는 아래 속성을 사용
156+
- `corePoolSize` : 스레드 풀에서 관리되는 기본 스레드의 수
157+
- `maximumPoolSize` : 스레드 풀에서 관리되는 최대 스레드 수
158+
- `keepAliveTime` , `TimeUnit unit` : 기본 스레드 수를 초과해서 만들어진 초과 스레드가 생존할 수 있는 대기 시간, 이 시간 동안 처리할 작업이 없다면 초과 스레드는 제거
159+
- `BlockingQueue workQueue` : 작업을 보관할 블로킹 큐
160+
161+
<br>
162+
163+
### Pool 생성 전략
164+
- 고정 스레드 풀 전략
165+
- **newFixedThreadPool(nThreads)**
166+
- 트래픽이 일정하고, 시스템 안전성이 가장 중요
167+
- 캐시 스레드 풀 전략
168+
- **newCachedThreadPool()**
169+
- 일반적인 성장하는 서비스
170+
- 사용자 정의 풀 전략
171+
- **ThreadPoolExecutor(...)**
172+
- 다양한 상황에 대응
173+
174+
대부분의 서비스는 트래픽이 어느정도 예측 가능하므로 일반적인 상황이라면 고정 스레드 풀 전략이나, 캐시 스레드 풀 전략을 사용하면 충분

0 commit comments

Comments
 (0)