[JAVA] Java 에서의 Thread 사용
Thread in Java
쓰레드란 프로그램 실행의 가장 작은 단위이다. 일반적으로는 Main 하나로 동작시키겠지만 웹 서버 등에서는 계속해서 클라이언트의 연결 요청을 받아줘야하기에 매 요청에 대해서 동시성을 지원해줘야한다.
Java 버전에 따른 스레드의 발전 과정 등에 대해서 알아보자.
Java 버전 | Thread |
---|---|
Java5 이전 | Runnable, Thread |
Java6 | Callable, Future 및 Executor, ExecutorService, Executors |
Java7 | Fork/Join 및 RecursiveTask |
Java8 | CompletableFuture |
Java9 | Flow |
Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Runnable 은 추상 메서드 run 을 가지는 인터페이스이다.
단 하나의 메서드를 가지기에 람다로 구현할 수 있으며 해당 내용이 곧 스레드가 실제 실행할 내용이다.
실제 웹 서버의 코드에서도 Thread 에 Runnable 인터페이스를 사용하여 간단하게 스레드를 구성하였다.
Thread
public class Thread implements Runnable {
...
}
Thread 는 클래스이다.
해당 클레스에서는 스레드를 멈추거나 Exception 을 발생시키고, 다른 스레드와의 작업 순서를 제어하는 등 직접적인 제어가 가능한 메서드들을 가진다.
Thread 는 말 그대로 클래스이기에 사용하기 위해서는 해당 클래스를 상속받아 run 메서드를 override 해야한다.
클래스이기에 많은 자원을 사용하고 상속 클래스를 구현해야하는 등 Runnable 보다도 더 많은 자원이 필요하기에 대부분 Runnable 을 사용한다.
결국 Thread, Runnable 둘 다 저수준 API 에 의존하며, 결과값을 반환하지 못하고, 스레드 시작과 종료에 대한 오버헤드 및 관리에 대한 어려움 문제를 가지고 있다.
ThreadPool
ThreadPool 은 매번 스레드를 시작 및 종료하는 것이 아니라 미리 스레드를 만들어놓고 필요에 따라 작업을 할당한다.
작업이 들어오면 Blocking Queue 를 통해 작업이 쌓이게 되고 순서대로 빈 스레드에 할당되어 작업을 실행한다.
try (ServerSocket listenSocket = new ServerSocket(port)) {
logger.info("Web Application Server started {} port.", port);
// 클라이언트가 연결될때까지 대기한다.
Socket connection;
ExecutorService executor = Executors.newFixedThreadPool(10);
while ((connection = listenSocket.accept()) != null) {
executor.submit(new RequestHandler(connection));
// Thread thread = new Thread(new RequestHandler(connection));
// thread.start();
}
}
스레드를 사용해 커넥션을 처리하는 과정에서도 10 개의 스레드풀을 미리 생성해 계속해서 재사용할 수 있다.
newCachedThreadPool() 을 사용해 필요한 만큼의 스레드풀을 생성할 수도 있다.
Executors.newFixedThreadPool();
ExecutorService executor = Executors.newFixedThreadPool(30);
newFixedThreadPool() 은 실제 미리 스레드를 모두 만들어놓는다. 때문에 계속해서 요청을 보내게되면 1~30 번까지의 스레드를 모두 이용하고 다시 1번 스레드부터 순서대로 사용하게 된다.
Executors.newCachedThreadPool();
ExecutorService executor = Executors.newCachedThreadPool();
반면에 newCachedThreadPool() 같은 경우에는 미리 스레드를 만들어놓지 않고 실제 사용되는 만큼의 스레드만 생성한다. 때문에 실제 결과에서도 1~10 번까지의 스레드들이 재사용되는 것을 볼 수 있다.
Callable, Future
기존 Runnable 인터페이스는 결과를 반환할 수 없다는 문제가 있다.
따라서 이후 결과를 받을 수 있는 Callable 이 추가되었다.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
함께 비동기 작업에 대한 결과 반환을 위한 Future 도 추가되었다.
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() 메서드를 통해서 실제 결과값을 반환받을 수 있다.
하지만 아직도 결과를 얻기 위해서는 Blocking 방식으로 대기해야한다는 단점이 있다. 이를 해결하기 위해 CompletableFuture 가 추가되었다.
CompletableFuture
기본적으로 Future 을 기반으로 외부에서 완료시킬 수 있기에 CompletableFuture 이다. 즉, 몇 초 이내로 응답이 안오면 기본값을 반환시키는 등의 작업이 가능해졌다. 뿐만 아니라 콜백 등록 및 Future 조합도 가능하기에 다방면으로 활용할 수 있다.
댓글남기기