Thread
- 스레드가 하나인 경우를 싱글 스레드, 하나 이상인 경우를 멀티 스레드라 부른다.
- 멀티 스레드를 사용하면 애플리케이션에서 여러개의 작업을 동시에 할 수 있다.
- 멀티 스레드를 사용하면 스케쥴링 알고리즘에 의해 스레드가 전환되면서 작업을 처리하는데 이를 컨텍스트 스위칭이라 부른다.
- 하나의 프로세스에는 최소 하나 이상의 스레드가 존재하고 프로세스 내에 스레드들은 동일한 메모리를 공유한다.
- 스레드가 무한정 많아지면 메모리 사용량이 높아져 OOME 가 발생할 수 있고 동시 처리량을 요구하는 시스템에서는 스레드를 생성하면서 발생하는 대기 시간 때문에 응답 지연이 발생한다.
- 이러한 문제를 해결하기 위해 스레드 풀을 사용해야 한다. 스레드 풀을 사용하면 애플리케이션 내에서 사용할 총 스레드 수를 제한할 수 있고 기존에 생성된 스레드를 재사용하므로 빠른 응답이 가능하다.
- 직접 만드는 것보다 검증된 라이브러리를 사용하자. java.util.concurrent 패키지의 ExecutorService 를 사용하면 쉽고 안전하게 스레드 풀을 사용할 수 있다.
요약: 하나의 프로세스에는 하나 이상의 스레드가 존재한다. 스레드를 여러개 사용하면 여러개의 작업을 동시에 할 수 있다.
프로세스 내에 스레드는 동일한 메모리를 공유한다. 스레드가 많아지면 메모리 사용량이 높아져 OutOfMemoryError 가 발생하거나,
스레드를 생성하기 위한 대기 시간 때문에 응답 지연이 발생할 수 있다. 이를 위해 스레드 풀을 사용하여 스레드 수를 제한하고 생성된 스레드를 재사용하는 방식을 택해야 한다. 스레드 풀 사용시 검증된 라이브러리 Concurrent 패키지의 ExecutorService 를 사용하자.
fun main() {
val pool: ExecutorService = Executors.newFixedThreadPool(5)
try {
for (i in 0 .. 5) {
pool.execute {
println("current-thread-name : ${Thread.currentTread().name}")
}
}
} finally {
pool.shutdown()
}
println("current-thread-name : ${Tread.currentThread().name}")
}
// pool-1-thread-1
// pool-1-thread-2
// pool-1-thread-3
// pool-1-thread-4
// pool-1-thread-5
// pool-1-thread-1
// main
Future
Future 는 비동기 작업에 대한 결과를 얻고 싶은 경우에 사용된다.
예를들어 수행 시간이 오래 걸리는 작업이나 작업에 대한 결과를 기다리면서 다른 작업을 병행해서 수행하고 싶은 경우에 유용하다.
스레드는 Runnable 을 사용해 비동기 처리를 하지만 퓨처를 사용해 처리 결과를 얻기 위해선 Callable을 사용한다.
fun sum(a: Int, b: Int) = a + b
fun main() {
val pool = Executors.newSingleThreadExecutor()
val future = pool.submit(Callable {
sum(100, 200)
})
println("계산 시작"
val futureResult = future.get() // 비동기 작업의 결과를 기다린다. (blocking)
// future.get(1000L, TimeUnit.MICROSECONDS) // 타임아웃 지정 가능
println(futureResult)
println("계산 종료")
}
// 계산 시작
// 300
// 계산 종료
Future 의 단점 블록킹이 된다.
jdk8 부터는 Completable Future 제공
fun sum(a: Int, b: Int) = a + b
fun main() {
val completableFuture = CompletableFuture.supplyAsync {
Thread.sleep(2000)
sum(100, 200)
}
println("계산 시작")
completableFuture.thenApplyAsync(::println) // nonBlocking
// val result = completableFuture.get() // Blocking
// println(result)
while(!completableFuture.isDone) {
Thread.sleep(500)
println("계산 결과를 집계 중입니다.")
}
println("계산 종료")
}
// Nonblocking 결과값
// 계산 시작
// 계산 결과를 집계 중입니다.
// 계산 결과를 집계 중입니다.
// 계산 결과를 집계 중입니다.
// 300 (thenApplyAsync 수행)
// 계산 결과를 집계 중입니다.
// 계산 종료
// Blocking 결과값
// 계산 시작
// 300
// 계산 종료
요약: Future 는 비동기 작업 결과를 얻고 싶은 경우에 사용한다. 스레드는 Runnable 인터페이스를 사용하여 비동기 처리를 하지만 Future 는 Callable 인터페이스를 사용한다. Future 의 경우 결과값을 기다리기 까지 Blocking 되어 응답지연을 발생시킬 수 있다. 해당 문제를 해결하기 위해 나온것이 CompletableFuture 이다. CompletableFuture 를 사용하면 결과값을 기다리는 동안 Nonblocking 되며 다른 작업을 동시에 수행할 수 있다. 많은 실무자들은 CompletableFuture 를 사용한다.
'Spring > Webflux' 카테고리의 다른 글
스프링 웹 플럭스 (Spring Webflux) (2) | 2024.01.28 |
---|---|
리액티브 프로그래밍이란 (0) | 2024.01.27 |
토비의 봄 TV - CompletableFuture (7) (0) | 2022.06.28 |
토비의 봄 TV - AsyncRestTemplate의 콜백 헬과 중복 작업 문제 (6) (0) | 2022.06.24 |
토비의 봄 TV - 스프링의 비동기 기술 (4) 2/2 (0) | 2022.06.17 |