이번에는 콜백(callback) 방식을 이용해서 작업 완료 통보를 받는 방법에 대해서 알아보자. 콜백이란 애플리케이션이 스레드에게 작업 처리를 요청한 후, 스레드가 작업을 완료하면 특정 메소드를 자동 실행하는 기법을 말한다. 이때 자동 실행되는 메소드를 콜백 메소드라고 한다. 다음은 블로킹 방식과 콜백 방식을 비교한 그림이다.
블로킹 방식은 작업 처리를 요청한 후 작업이 완료될 때까지 블로킹되지만, 콜백 방식은 작업 처리를 요청한 후 결과를 기다릴 필요 없이 다른 기능을 수행할 수 있다. 그 이유는 작업 처리가 완료되면 자동적으로 콜백 메소드가 실행되어 결과를 알 수 있기 때문이다.
아쉽게도 ExecutorService는 콜백을 위한 별도의 기능을 제공하지 않는다. 하지만 Runnable 구현 클래스를 작성할 때 콜백 기능을 구현할 수 있다. 먼저 콜백 메소드를 가진 클래스가 있어야 하는데, 직접 정의해도 좋고 java.nio.channels.CompletionHandler를 이용해도 좋다. 이 인터페이스는 NIO 패키지에 포함되어 있는데 비동기 통신에서 콜백 객체를 만들 때 사용된다. 그럼 CompletionHandler를 이용해서 콜백 객체를 만드는 방법을 살펴보자. 다음은 CompletionHandler 객체를 생성하는 코드이다.
CompletionHandler<V, A> callback = new CompletionHandler<V, A>() {
@Override
public void Completed(V result, A attachment) {
}
@Override
public void failed(Throwable exc, A attachment) {
}
};
CompletionHandler는 completed()와 failed() 메소드가 있는데, completed()는 작업을 정상 처리 완료했을 때 호출되는 콜백 메소드이고, failed()는 작업 처리 도중 예외가 발생했을 때 호출되는 콜백 메소드이다. CompletionHandler의 V타입 파라미터는 결과값의 타입이고, A는 첨부값의 타입이다. 첨부값은 콜백 메소드에 결과값 이외에 추가적으로 전달하는 객체라고 생각하면 된다. 만약 첨부값이 필요 없다면 A는 Void로 지정해주면 된다. 다음은 작업 처리 결과에 따라 콜백 메소드를 호출하는 Runnable 객체이다.
Runnable task = new Runnable() {
@Override
public void run() {
try {
// 작업 처리
V result = ..;
callback.completed(result, null);
} catch(Exception e) {
callback.failed(e, null);
}
}
};
작업 처리가 정상적으로 완료되면 completed() 콜백 메소드를 호출해서 결과값을 전달하고, 예외가 발생하면 failed() 콜백 메소드를 호출해서 예외 객체를 전달한다. 다음은 두 개의 문자열을 정수화해서 더하는 작업을 처리하고 결과를 콜백 방식으로 통보한다. 첫 번째 작업은 "3", "3"을 주었고 두 번째 작업은 "3", "삼"을 주었다. 첫 번째 작업은 정상적으로 처리되기 때문에 completed()가 자동 호출되고, 두 번째 작 업은 NumberFormatException이 발생되어 failed() 메소드가 호출된다.
// CallbackExample.java -- 콜백 방식의 작업 완료 통보받기
public class CallbackExample {
private ExecutorService executorService;
public CallbackExample() {
executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
}
private CompletionHandler<Integer, Void> callback =
new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {
System.out.println("completed() 실행: " + result);
}
@Overrdie
public void failed(Throwable exc, Void attachment) {
System.out.println("failed() 실행: " + exc.toString());
|
};
public void doWork(final String x, final String y) {
Runnable task = new Runnable() {
@Override
public void run() {
try {
int intX = Integer.parseInt(x);
int intY = Integer.parseInt(y);
int result = intX + intY;
callback.completed(result, null);
} catch(NumberFormatException e) {
callback.failed(e, null);
}
}
};
executorService.submit(task); // 스레드풀에게 작업 처리 요청
}
public void finish() {
executorService.shutdown(); // 스레드풀 종료
}
public static void main(String[] args) {
CallbackExample example = new CallbackExample();
example.doWork("3", "3");
example.doWork("3", "삼");
example.finish();
}
}
'Java 길찾기 > 이것이 자바다' 카테고리의 다른 글
[Java] 제네릭 타입(class<T>, interface<T>) (0) | 2022.03.31 |
---|---|
[Java] 왜 제네릭을 사용해야 하는가? (0) | 2022.03.30 |
[Java] 스레드풀 - 블로킹 방식의 작업 완료 통보 (0) | 2022.03.28 |
[Java] 스레드풀 - 생성, 종료, 작업생성, 처리요청 (0) | 2022.03.25 |
[Java] 스레드 그룹 (0) | 2022.03.24 |