요즘 이력서 쓰느라... 면접 보느라... 코테 하느라... 너무 바쁜 일상을 보내고 있다. 어제는 여자친구랑 불꽃축제를 구경하며 리프레시를 했지만 해야할게 너무 많다!!
오늘은 쓰레드에 대해 좀 더 약간 공부를 해볼려고 한다...(쓰레드는 너무 어려워... 😭)
예전에 프로세스, 쓰레드에 대해 조금 정리를 해두긴 했다.
자바에서 스레드를 만드는 방법은 2가지가 존재한다. 하나는 Thread 클래스를 상속 받는 방법, 또 하나는 Runnable 인터페이스를 구현하는 방법이 있다.
하지만 이 두가지 방법에는 단점 및 한계가 존재한다!
1 . 저수준 API 의존
2. 값 반환 불가
3. 오버헤드 발생
매번 새로운 Thread를 만들고 종료하면, 스레드 생성/소멸 비용이 있음
많은 요청이 들어오는 환경에서는 성능 문제 발생 가능
4. 스레드 관리 어려움
여러 스레드를 동시에 제어하거나, 상태를 추적하는 게 번거로움
Deadlock, Race Condition 같은 동시성 문제에 직접 대응해야 함
따라서 Java5 부터는 결과를 반환할 수 있도록 Callable 과 Future를 사용할 수 있도록 한다!!
Runnable의 발전된 형태로써, 제네릭을 사용해 결과를 받을 수 있다!!
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
// Runnable: 결과 없음
Runnable r = () -> System.out.println("작업 수행");
// Callable: 결과 있음
Callable<Integer> c = () -> {
Thread.sleep(1000);
return 42; // 결과 반환 가능
};
| 항목 | Runnable | Callable |
|---|---|---|
| 반환값 | 없음 (void run()) | 있음 (V call()) |
| 예외 처리 | checked 예외 던질 수 없음 → try-catch 필요 | checked 예외 던질 수 있음 (throws Exception) |
| 실행 방법 | new Thread(runnable).start() | ExecutorService.submit(callable) |
| 스레드 풀 사용 | 가능 (execute()) 하지만 결과 없음 | submit() 통해 Future<V> 결과 받음 |
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
get()get(long timeout, TimeUnit unit))isDone() / isCancelled()isDone() : 작업의 완료 여부 반환 (boolean)isCancelled() : 작업의 취소 여부 반환 (boolean)cancel()boolean)cancel() 호출 후에는 isDone()이 항상 true 반환true를 전달하면, 쓰레드를 interrupt -> InterruptException 발생
간단하게 말하자면 요청(Request) 하나당 하나의 쓰레드(Thread)를 생성하는 방식이다.
Spring Web MVC를 예를 들다면, 1개의 요청에 1개의 쓰레드를 사용하게 되고, Controller, Service, Repository, model 어디에서든 같은 쓰레드를 사용하게 된다.
하지만 이렇게 매 요청마다 쓰레드를 생성하면 비용이 많이 발생한다. 요청이 많아질수록 각 쓰레드는 CPU와 메모리를 많이 잡아먹고 Out Of Memory가 발생할 수도있고, 오버헤드가 많이 발생한다.
따라서 쓰레드를 제한된 개수 만큼 정해두고, 작업 큐(Queue)에 들어오는 작업들을 하나의 쓰레드가 맡아처리하고, 작업처리가 끝난 쓰레드는 다시 작업 큐(Queue)에서 새로운 작업을 처리하는 쓰레드풀이 등장하였다.
Java5에서는 이러한 쓰레드 풀에 대한 기능이 추가되었고, Executor, ExecutorService를 제공한다.

쓰레드 풀(Thread Poll)의 구현을 위한 인터페이스
등록된 작업(Runnable)을 실행하기 위한 인터페이스
작업 등록과 작업 실행 중에서 작업 실행만을 책임
public interface Executor {
void execute(Runnable command);
}
쓰레드는 크게 작업의 등록, 실행으로 나눈다. 그중에서 Executor는 ISP 원칙에 따라 등록된 작업을 실행하는 책임만 갖는다. 그래서 전달받은 작업(Runnable)을 실행하는 메서드만 존재한다.
public class ExecutorExample {
@Test
void executorRunAsync() {
final Runnable runnable = () -> System.out.println("Thread: " + Thread.currentThread().getName());
Executor executor = new AsyncExecutor();
executor.execute(runnable);
System.out.println("Main Thread: " + Thread.currentThread().getName());
}
// Executor를 구현, Runnable을 새로운 Thread에서 실행
static class AsyncExecutor implements Executor {
@Override
public void execute(final Runnable command) {
new Thread(command).start(); // 새로운 스레드에서 실행
}
}
}
Runnable 작업을 비동기로 실행, Executor 인터페이스를 구현하여 실행 방식을 추상화, 메인 스레드와 작업 스레드가 독립적으로 실행됨

Executor를 상속받아 작업 등록 뿐만 아니라 실행을 위한 책임도 갖는다.TreadPoolExecutor가 ExcutorService의 구현체인데, ThreadPoolExecutor 내부에 있는 블로킹 큐에 작업을 등록해둔다.ExecutorService는 비동기 작업을 위한 기능들을 제공해준다고 한다. 특히, 비동기 작업의 진행을 추적할 수 있도록 Future를 반환한다. 반환된 Future들은 모두 실행된 것이므로 반환된 isDone = true
submitFuture를 반환 Future.get()을 호출하면 작업이 성공적으로 완료된 후 결과를 얻을 수 있음invokeAllList<Future>를 반환invokeAnyFuture로 반환AbstractExecutorService와 ExecutorService 메서드 구현AbstractExecutorService는 ExecutorService의 기본 구현을 제공 submit, invokeAll, invokeAny 등 메서드의 기본 동작을 제공함invokeAll 동작 방식invokeAny 동작 방식
블로그를 마무리하며 어제 찍은 불꽃축제 사진을 올려본다.
어둠을 뚫고 터져 나오는 저 찬란한 빛들을 보니, 문득 우리 모두가 저 불꽃처럼 살았으면 좋겠다는 생각이 든다. 각자의 자리에서 자신만의 색깔로 밝게 빛나며, 주변을 따뜻하게 비춰주는 그런 사람들로 말이다.
짧지만 강렬한 순간을 위해 모든 걸 쏟아내는 불꽃처럼, 오늘도 누군가에게 작은 빛이 되어주길 바란다.