자바 1.7 이후 부터 개발자들은 ExcutorService 프레임웍을 활용하여 손쉽게 병렬 프로그램을 작성할 수 있다.
이번에 소개할 ExecutorCompletionService 객체는 여러 비동기 작업을 실행하고, 완료된 작업의 결과를 순서대로 가져오는 클래스다
위 소개에 언급한것과 마찬가지로 완료된 작업을 순서대로 가져와야 하기때문에
해당 작업을 객체는 Callable을 구현해야한다.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
위처럼 제네릭으로 선언되어있는것 처럼 값을 반환 받을 수 있어야 한다.
* Runnable 구현받은 작업은 제출한 값에 대해서 값을 받을 수 없다.
우선 이번예제는 각 이미지 url을 배열에 두고 해당 이미지를 불러와서 화면에 그려주는 예제를 보여주겠다.
우선 기존 ExcutorService 에 제출한 작업의 future.get() 하여 순차적으로 작업하는 예를 보자.
코드: 단순 Future.get() 방식 (CompletionService 없이 실행)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ImageLoaderWithoutCompletionService {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
String[] imageUrls = {
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg",
"https://example.com/image4.jpg",
"https://example.com/image5.jpg"
};
// 1. Future 리스트 생성
List<Future<String>> futures = new ArrayList<>();
// 2. 각 이미지 로딩 작업을 submit()하여 Future 리스트에 저장
for (String imageUrl : imageUrls) {
Future<String> future = executorService.submit(() -> loadImage(imageUrl));
futures.add(future);
}
// 3. Future.get()을 호출하여 결과 가져오기 (제출 순서대로 처리됨)
for (Future<String> future : futures) {
try {
String loadedImage = future.get(); // 블로킹 (해당 작업이 끝날 때까지 대기)
renderImage(loadedImage);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
// 이미지 로딩을 시뮬레이션하는 함수 (네트워크 요청 대신 sleep 사용)
public static String loadImage(String imageUrl) throws InterruptedException {
int loadTime = (int) (Math.random() * 3000) + 1000; // 1~3초 랜덤 로딩 시간
Thread.sleep(loadTime);
System.out.println("Loaded: " + imageUrl + " (" + loadTime + "ms)");
return imageUrl;
}
// 로드된 이미지를 렌더링하는 함수
public static void renderImage(String imageUrl) {
System.out.println("Rendered: " + imageUrl);
}
}
이번 코드에서 우리가 주목해야하는 부분은 주석 3 번 부분이다. Future.get() 함수는 블록킹 메서드이며
모든 이미지의 로딩이 끝날때 까지 renderImage 메서드를 호출할 수 없다.
뭔가 아쉽지 않은가.. 각 이미지 마다 먼저 로딩이 완료된 이미지 부터 rednerImge 메서드를 호출 하여 화면을 업데이트 할 수 있다면
더 성능에 최적화 되지 않을까 라는 생각이 든다.
위 코드에 대해서 좀 더 최적화를 해보자면
ExcutorCompletionService 클래스를 사용하여 더 빠르게 각 이미지들을 화면에 업데이트 할 수 있을것이다.
코드: 비동기 이미지 로딩 및 렌더링
import java.util.concurrent.*;
public class ImageLoaderExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletionService<String> completionService = new ExecutorCompletionService<>(executorService);
String[] imageUrls = {
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg",
"https://example.com/image4.jpg",
"https://example.com/image5.jpg"
};
// 1. 각 이미지 로딩 작업을 비동기 실행
for (String imageUrl : imageUrls) {
completionService.submit(() -> loadImage(imageUrl));
}
// 2. 완료된 이미지부터 순서대로 렌더링
for (int i = 0; i < imageUrls.length; i++) {
try {
Future<String> future = completionService.take();
String loadedImage = future.get();
renderImage(loadedImage);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
// 이미지 로딩을 시뮬레이션하는 함수 (네트워크 요청 대신 sleep 사용)
public static String loadImage(String imageUrl) throws InterruptedException {
int loadTime = (int) (Math.random() * 3000) + 1000; // 1~3초 랜덤 로딩 시간
Thread.sleep(loadTime);
System.out.println("Loaded: " + imageUrl + " (" + loadTime + "ms)");
return imageUrl;
}
// 로드된 이미지를 렌더링하는 함수
public static void renderImage(String imageUrl) {
System.out.println("Rendered: " + imageUrl);
}
}
역시 이번 코드에 주목할 부분은 바로 주석 2번 구역이다
기존에는 Future.get() 블록킹 메서드에서 모든 이미지가 로딩이 완료 될때까지 기다렸다가
한번에 renderImage 메서드가 호출되는 방식이였다면,
이번 코드는 이제 completionService.take() 메서드를 통해서 먼저 로딩이 완료된 이미지는 먼저 take() 하여 바로 renderImage 메서드를 호출 할 수 있다.
실행 예시
Loaded: https://example.com/image3.jpg (1265ms)
Loaded: https://example.com/image1.jpg (1803ms)
Loaded: https://example.com/image5.jpg (2056ms)
Rendered: https://example.com/image3.jpg
Rendered: https://example.com/image1.jpg
Loaded: https://example.com/image2.jpg (2780ms)
Rendered: https://example.com/image5.jpg
Loaded: https://example.com/image4.jpg (3210ms)
Rendered: https://example.com/image2.jpg
Rendered: https://example.com/image4.jpg
어떤 이미지가 먼저 로딩이 완료될지는 알 수 없다
하지만 먼저 완료된 이미지에 한해서는 먼저 가져와 다른 작업을 하여 더 효율적으로 클라이언트측에 이미지를 렌더링 할 수 있다.
'it 서적 독후감' 카테고리의 다른 글
Java 병렬프로그래밍 chap 1 (4) | 2025.02.24 |
---|---|
함수형 코딩 (0) | 2024.07.30 |