ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java Future 알아보기
    Language/Java 2023. 9. 30. 10:20
    728x90
    반응형

    - 목차

     

     

    관련된 글

    https://westlife0615.tistory.com/319

     

    Java Thread Pool 알아보기

    - 목차 소개. Thread Pool 은 Worker Thread 들을 관리하는 자료구조입니다. Thread Pool 은 최대로 할당 가능한 Thread 갯수를 제한하며, Worker Thread 를 재사용할 수 있는 효율적인 방법은 제공합니다. 즉, Threa

    westlife0615.tistory.com

     

     

    소개.

    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 를 맞출 수 있게 됩니다.

    반응형
Designed by Tistory.