-
Java Future 알아보기Java 2023. 9. 30. 10:20728x90반응형
- 목차
관련된 글
https://westlife0615.tistory.com/319
소개.
java 의 Future 는 비동기 처리를 수행하도록 돕는 대상이자 java class 입니다.
Future 는 javascript 의 Promise 와 유사한 행동양식을 보이며, 미래에 완료될 Task 를 의미합니다.
Main Thread 에서 코드들이 처리되고 있다고 한다면, Future 를 사용함으로써 다른 Thread 에 비동기 작업을 위임할 수 있고,
다른 Thread 에서 처리된 작업은 처리가 완료된 이후에 Main Thread 에 처리 결과를 제공하고 데이터를 공유할 수 있습니다.
예를 들어, 어떤 file 들을 읽어들이는 작업이 있다고 가정하겠습니다.
이 작업은 여러 개의 File 을 읽어들여야합니다.
파일을 하나씩 순차적으로 읽는 것보다 동시에 여러 파일을 읽어들이는 것이 시간적으로 효율적일 수 있습니다.
이러한 경우에 비동기 처리가 필요하죠.
Network IO, Disk IO, 복잡한 연산을 수행할 때, Future 를 사용할 수 있습니다.
결과적으로 Future 를 사용함으로써 얻는 장점은 3가지 입니다.
1. 비동기처리를 수행할 수 있다.
2. Parent Thread 에게 비동기 작업의 결과를 공유할 수 있다.
3. Thread 간의 Sequence 를 맞출 수 있다.
Runnable & Callable
Future 를 실행하기 위한 구성요소입니다.
Runnable 과 Callable 모두 Thread 의 실질적인 수행 코드를 가지는데요.
Runnable 은 반환하는 값이 없는 비동기 작업을,
Callable 은 반환 가능한 비동기 작업을 의미합니다.
Thread vs Future.
Thread 를 단독으로 사용하는 방식과 Future 를 사용하는 방식의 차이점을 아는 것이 중요합니다.
Future 는 java 의 java.util.concurrency 패키지의 클래스로 java 5 버전부터 등장하였습니다.
이는 Thread 가 가지는 몇가지 단점들을 보완한 개념인데요.
각각 비동기 처리를 가능하게 하는 공통점 이외에 차이점을 아는 것이 중요합니다.
그 차이점이 Thread 의 보완점과 Future 의 등장 배경입니다.
Exception Handling.
Parent Thread 와 Child Thread 가 있다고 가정하겠습니다.
Parent Thread 는 Main Thread 처럼 다른 Thread 를 생성하고 관리하는 주체입니다.
Thread 만 단독으로 사용하는 경우에는 Child Thread 에서 발생하는 여러 예외 상황을 핸들링하기가 까다롭습니다.
두 Parent Thread, Child Thread 간의 통신하는 체계가 잘 짜여져 있지 않기 때문이죠.
Child Thread 가 예기치 못하게 종료될 경우,
- Child Thread 가 사용 중이던 리소스들을 알맞게 반환해주거나
- Child Thread 가 처리 중인 작업이 Side-Effect 가 있다면 그에 따른 대처를 해야합니다.
예를 들어,
아래와 같이 Child Thread 가 어떤 File 을 Read 하는 작업을 수행하고 있다고 가정하겠습니다.
File Open 이후에 갑작스레 작업이 종료되어 File close 를 하지 않은 상황인 경우에 해당 File Descriptor 를 반환하지 않아
해당 프로세스는 계속 File Descriptor 정보를 가지고 있게 됩니다.
package org.example; import java.io.File; import java.io.FileInputStream; import java.util.concurrent.ExecutionException; public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { Thread childThread = new Thread(() -> { File file = new File("/tmp/test"); FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.read(); throw new RuntimeException(e); }); childThread.start(); } }
만약 Future 를 사용하는 경우에는
Child Thread 의 비정상 종료를 Parent Thread 에서 핸들링할 수 있게 됩니다.
package org.example; import java.io.File; import java.io.FileInputStream; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService service = Executors.newFixedThreadPool(10); Future future = service.submit(() -> { File file = new File("/tmp/test"); FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.read(); throw new RuntimeException("error"); }); try { future.get(); } catch (Exception e) { // Exception Handling here System.out.println(""); } } }
Network IO.
Future 를 활용한 Network IO 관련 예시를 살펴보겠습니다.
package org.example; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { private static String fetchHtmlTitle(String url) throws Exception { URLConnection connection = (new URL(url)).openConnection(); Pattern pattern = Pattern.compile("<div class=\"title_view\">.*?</div>"); try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { String line; StringBuilder content = new StringBuilder(); while ((line = in.readLine()) != null) { content.append(line); } Matcher matcher = pattern.matcher(content.toString()); if (matcher.find()) { return matcher.group(); } } return ""; } public static void main(String[] args) throws ExecutionException, InterruptedException { String url1 = "https://westlife0615.tistory.com/317"; String url2 = "https://westlife0615.tistory.com/297"; String url3 = "https://westlife0615.tistory.com/296"; String url4 = "https://westlife0615.tistory.com/294"; ExecutorService executorService = Executors.newFixedThreadPool(4); Future<String> future1 = executorService.submit(() -> fetchHtmlTitle(url1)); Future<String> future2 = executorService.submit(() -> fetchHtmlTitle(url2)); Future<String> future3 = executorService.submit(() -> fetchHtmlTitle(url3)); Future<String> future4 = executorService.submit(() -> fetchHtmlTitle(url4)); System.out.println(future1.get()); System.out.println(future2.get()); System.out.println(future3.get()); System.out.println(future4.get()); } }
<실행 결과>
<div class="title_view">RxJava Observable 알아보기</div> <div class="title_view">Collaborative Filtering 이해하기</div> <div class="title_view">RSA 암호화 수학적 원리 이해하기</div> <div class="title_view">HDD (hard disk drive) 깊이 이해하기</div>
Thread Pool.
Future 는 단독으로 사용할 수 있지만, 보통 Thread Pool 과 함께 사용됩니다.
ExecutorService 를 Thread Pool 를 관리하는 요소입니다.
아래와 같은 형식으로 Thread Pool 를 쉽게 생성할 수 있습니다.
ExecutorService service = Executors.newFixedThreadPool(10);
그리고 ExecutorService 의 submit 메소드를 통해서 Runnable 이나 Callable 를 Thread Pool 에 추가할 수 있는데요.
Thread Pool 에 추가된 Runnable 또는 Callable 은 Task Queue 라고 불리는 자료구조에 추가됩니다.
Task Queue 에 추가된 Runnable 또는 Callable 는 Thread Pool 에 의해서 실행되는 Task 입니다.
ExecutorService.submit 의 결과로 Future 가 반환되는데요.
Future 는 미래에 완료될 비동기 작업을 뜻합니다.
submit 이후에 Thread 가 Scheduling 되면 해당 Runnable 또는 Callable 은 바로 수행됩니다.
그리고 Future 의 get 메소드를 통해서 Parent Thread 로 비동기 작업의 결과를 공유할 수 있습니다.
이렇게 Thread 간의 Sequence 를 맞출 수 있게 됩니다.
반응형'Java' 카테고리의 다른 글
[Java] 익명 클래스와 메모리 관계 알아보기 ( Anonymous Class , Method Area ) (0) 2024.06.02 Jackson 으로 JSON 다루기 (0) 2024.01.22 Java off-heap 메모리 알아보기 (0) 2023.12.22 Java Thread Pool 알아보기 (0) 2023.09.30 java Annotation 이해하기 (0) 2023.09.25