[Java] 멀티 스레드(Multi Thread) (7) : 스레드풀, 작업의 완료 1
지연 완료 객체 (Pending Completion Object) : submit()
6편에서 다뤘듯이 submit()에 의해 처리가 요청된 작업은 종료시 처리 결과를 Future 객체로 리턴한다. 이때 Future 객체는 get() 메서드를 통해 결과값을 리턴 받을 수 있는데 get()을 호출하면 스레드가 작업을 완료할때까지 블로킹 되었다가 작업이 완료되면 결과를 리턴한다. 그래서 Future을 지연 완료 객체라고 한다.
get() 메서드를 호출할 경우, 모든 작업이 블로킹 되기 때문에 get() 메소드는 새로운 스레드거나, 스레드 풀의 다른 스레드에서 실행 되어야 한다.
리턴값이 없는 경우
submit() 메서드의 인자로 Runnable 객체를 줄 경우, 리턴값이 없게 된다. 이때 Future.get() 메서드는 null 값을 리턴한다. 하지만 만약 작업의 처리가 완료 되지 않았을 경우, InterruptedException을 발생시키고, 작업이 처리 도중 예외가 발생했을 경우, ExecutionException을 발생시킨다. 이때 Future 객체는 결과 값을 리턴하지는 않지만 해당 작업이 예외 없이 처리되었는지 확인하는데 사용이 된다.
NoReturnExample.java
public class NoReturnExample {
public static void main(String[] args) {
ExecutoreService executorService = Executors.newFixedThreadPool(10);
Runnable runnable = new Runnable() { ... };
Future future = executorService.submit(runnable);
try {
future.get();
System.out.println("[작업 완료]");
} catch (Exception e) {
System.out.println("[예외 발생] : " + e.getMessage());
}
}
}
리턴값이 있는 경우
submit() 메서드의 인자로 callable 객체를 넣게 되면 리턴값을 갖게 된다. 이때 이 리턴값을 Future.get() 메서드로 받을 수 있다. 하지만 이때 주의할 점이 하나 있는데 callable 객체의 제내릭 타입 T를 call() 메서드가 리턴하는 타입으로 설정해주어야 한다.
Callable<T> task = new Callable<>() {
@Override public T call() {
...
return T;
}
}
ReturnExample.java
public class ReturnExample {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Callable<Integer> task = new Callable<>() {
@Override public void call() {
int sum = 0;
for (int i=0; i<=10; ++i) { sum += i; }
return sum;
}
};
Future<Integer> future = executorService.submit(callable);
try {
int result = future.get();
System.out.println(result);
} catch (Exception e) {
System.out.println("[예외 발생] : " + e.getMessage);
}
}
다음은 0부터 10까지 더하는 작업을 하고 그 결과를 리턴하는 작업을 callabe 객체를 통해 정의하고 처리하여 결과값을 출력하는 프로그램이다. 보다시피 Callble의 제네릭 파라미터 T가 Integer로 설정되어 있고, call() 메서는 Integer 타입의 sum을 출력한다. 또한 Future 객체 역시 Integer 타입으로 설정된다. Future.get() 메서드는 call()메서드가 끝날때 까지 블로킹 된다.
작업 처리 결과를 객체에 저장하기
상황에 따라 작업의 처리 결과를 다른 객체에 저장해야 될 때가 있다. 예를 들어 작업 처리 결과를 외부에서 이용해야 될 경우, 객체를 통해 받고 그 결과를 이용해야 한다. 보통 이러한 객체, 외부 result객체는 공유 객체로 이용되어 여러 스레드의 결과를 취합하는 역할을 한다.
이를 하기 위해서는 일단 Result 객체를 생성하고, submit() 메서드의 두번째 인자로 넣어주면 된다.
Future<V> future = executorService(task, result);
이때 V는 result의 타입이다. 이때 result객체와 future객체의 차이는 future 객체에는 작업 처리 결과값이 담겨 있다.
만약 Runnbale 객체를 이용할 경우 리턴값이 없어 위 방법으로는 결과값을 객체에 넣을 수 없다. 그렇기 때문에 runnbale 객체에 결과값을 넣어줄 필드를 선언하고, runnbale 객체의 생성자를 통해 결과 값을 result 객체에 넣어준다.
class Task implements Runnable {
Result result; // 외부 result 객체
Task (Result result) { this.result = result; }
@Override public void run() { ... }
}