본문 바로가기

Java

Callable & ExecutorService

Callable

자바에서 Callable 인터페이스는 멀티스레드 프로그래밍에서 비동기적으로 실행할 작업을 나타내는데 사용됩니다. Runnable 인터페이스와 유사하게 작동하지만, Callable은 작업 실행 후 결과를 반환할 수 있으며, 실행 중에 예외를 던질 수 있다는 점에서 차이가 있습니다.

기본 구조

Callable 인터페이스는 java.util.concurrent 패키지에 정의되어 있으며, 다음과 같은 단일 메서드를 가지고 있습니다:

public interface Callable<V> {
    V call() throws Exception;
}


여기서 V는 메서드가 반환할 결과의 타입입니다.

사용 방법

Callable 인터페이스를 구현하는 클래스는 call() 메서드를 구현해야 하며, 이 메서드는 비동기적으로 실행될 작업의 내용을 정의합니다. call() 메서드는 작업의 실행이 완료되면 결과를 반환합니다.

Callable 객체는 보통 ExecutorService를 통해 실행됩니다. ExecutorService의 submit(Callable<V> task) 메서드를 사용하여 Callable 객체를 실행할 수 있으며, 이 메서드는 Future<V> 객체를 반환합니다. Future<V> 객체를 사용하여 Callable 작업의 결과를 나중에 검색할 수 있습니다.

예제

import java.util.concurrent.*;

public class Main {

    static ExecutorService executor = Executors.newCachedThreadPool();

    static Callable<Integer> callableTask = () -> {
        System.out.println("Run callableTask");
      return 123;
    };

    static Runnable runnableTask = () -> {
        System.out.println("Run runnableTask");
    };

    public static void runCallableTask() {
        Future<Integer> future = executor.submit(callableTask);

        try {
            Integer result = future.get();
            System.out.println("callable task 결과: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void runRunnableTask() {
        executor.execute(runnableTask);
    }

    public static void main(String[] args) {

        runCallableTask();

        runRunnableTask();

        executor.shutdown();

        System.out.println("main thread goodbye!");
    }
}


특징

  • Callable을 사용하면 실행 중인 작업으로부터 결과를 반환받을 수 있습니다.
  • Callable은 예외를 처리하기 용이하게 해줍니다. call() 메서드는 예외를 던질 수 있으며, 이 예외는 Future.get() 메서드를 호출할 때 ExecutionException으로 포장되어 던져집니다.
  • Future.get() 메서드는 작업이 완료될 때까지 현재 스레드를 블록합니다. 필요한 경우 Future.get(long timeout, TimeUnit unit) 메서드를 사용하여 최대 대기 시간을 지정할 수 있습니다.

    Callable은 결과를 반환하거나 예외 처리가 필요한 복잡한 비동기 작업을 수행할 때 유용합니다.

 

 

ExecutorService

ExecutorService는 자바의 java.util.concurrent 패키지에 있는 인터페이스로, 스레드 풀을 관리하고 비동기적으로 태스크를 실행하는 메커니즘을 제공합니다. 이 인터페이스를 사용하면 멀티스레드 애플리케이션을 더 쉽게 구현할 수 있으며, 스레드의 생성, 실행, 종료 등을 직접 관리하는 복잡성을 추상화합니다.

주요 기능

  • 태스크 실행: `ExecutorService`는 `Runnable`과 `Callable` 인터페이스를 구현하는 태스크를 비동기적으로 실행할 수 있습니다. `Callable` 태스크는 실행 결과를 반환할 수 있으며, 예외 처리도 가능합니다.
  • 스레드 풀 관리: 스레드 풀을 사용하여 태스크를 실행함으로써, 스레드의 생성과 소멸에 드는 비용을 줄이고, 시스템 리소스를 효율적으로 사용할 수 있습니다.
  • 태스크 결과 관리: `Future` 인터페이스를 통해 비동기 실행의 결과를 관리하고, 필요한 경우 결과가 준비될 때까지 대기하거나 태스크 실행을 취소할 수 있습니다.
  • 생명주기 관리: `ExecutorService`는 실행 중인 모든 태스크를 셧다운하고 스레드 풀을 종료하는 메커니즘을 제공합니다. 이를 통해 애플리케이션이 종료될 때 자원을 적절히 정리할 수 있습니다.

사용 방법

ExecutorService 인스턴스는 보통 Executors 유틸리티 클래스의 팩토리 메소드를 통해 생성합니다. 다음은 몇 가지 예시입니다:

ExecutorService executor = Executors.newSingleThreadExecutor(); // 단일 스레드 풀
ExecutorService executor = Executors.newFixedThreadPool(10); // 고정 크기 스레드 풀
ExecutorService executor = Executors.newCachedThreadPool(); // 요청에 따라 스레드 수가 변하는 풀


태스크를 실행하기 위해 submit 메소드를 사용할 수 있으며, Future 객체를 반환받아 태스크의 결과나 상태를 확인할 수 있습니다.

예제

package org.example.executorservice;

import java.util.concurrent.*;

public class Main {

    static ExecutorService executor = Executors.newCachedThreadPool();

    static Callable<Integer> callableTask = () -> {
        System.out.println("Run callableTask");
      return 123;
    };

    static Runnable runnableTask = () -> {
        System.out.println("Run runnableTask");
    };

    public static void runCallableTask() {
        Future<Integer> future = executor.submit(callableTask);

        try {
            Integer result = future.get();
            System.out.println("callable task 결과: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void runRunnableTask() {
        executor.execute(runnableTask);
    }

    public static void main(String[] args) {

        runCallableTask();

        runRunnableTask();

        executor.shutdown();

        System.out.println("main thread goodbye!");
    }
}


ExecutorService를 사용하면 스레드 관리의 복잡성을 크게 줄이면서, 비동기 태스크를 효율적으로 실행하고 관리할 수 있습니다.

 

ScheduledExecutorService

ScheduledExecutorService는 자바의 java.util.concurrent 패키지에 있는 ExecutorService를 확장한 인터페이스로, 스케줄링된 방식으로 태스크를 실행할 수 있는 기능을 제공합니다. 이 인터페이스를 사용하여 단일 실행 또는 반복 실행 태스크를 지정된 지연 시간 또는 주기적으로 실행할 수 있습니다. ScheduledExecutorService는 타이머나 스케줄러의 기능을 멀티스레드 환경에서 사용하기 위한 고수준의 추상화를 제공합니다.

주요 메서드

ScheduledExecutorService는 다음과 같은 주요 메서드를 제공합니다:

  • schedule(Callable<V> callable, long delay, TimeUnit unit): 지정된 지연 시간 후에 Callable 태스크를 한 번 실행합니다.
  • schedule(Runnable command, long delay, TimeUnit unit): 지정된 지연 시간 후에 Runnable 태스크를 한 번 실행합니다.
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit): 초기 지연 시간이 지난 후 주어진 주기마다 Runnable 태스크를 반복 실행합니다. 태스크의 실행 시간이 주기보다 길 경우, 다음 태스크는 이전 태스크가 끝난 직후 즉시 실행됩니다.
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit): 초기 지연 시간이 지난 후 태스크가 실행을 마친 후 주어진 지연 시간을 기다린 후 Runnable 태스크를 반복 실행합니다.


사용 예제

package org.example.schexecutorservice;

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Future;

public class Main {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        Runnable task = () -> System.out.println("Runnable 태스크 실행: " + new Date());
        Callable<String> callableTask = () -> {
            System.out.println("Callable 태스크 실행: " + new Date());
            return "결과";
        };

        // Runnable 태스크를 5초 후에 실행
        scheduler.schedule(task, 5, TimeUnit.SECONDS);

        // Callable 태스크를 10초 후에 실행
        Future<String> result = scheduler.schedule(callableTask, 10, TimeUnit.SECONDS);

        // 초기 지연 시간 없이, 2초마다 반복 실행
        scheduler.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);

        // 태스크 실행 완료 후 3초 지연을 두고 반복 실행
        scheduler.scheduleWithFixedDelay(task, 0, 3, TimeUnit.SECONDS);

        // 예정된 태스크들이 실행되는 동안 대기
        Thread.sleep(15000); // 15초 동안 실행

        // 스케줄러 종료
        scheduler.shutdownNow();
        System.out.println("스케줄러 종료.");

        // 스케줄러가 완전히 종료될 때까지 대기
        if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
            System.err.println("스케줄러가 지정된 시간 내에 종료되지 않았습니다.");
        }

        // Callable 태스크의 결과가 준비되었는지 확인하고 출력
        if (result.isDone()) {
            System.out.println("Callable 태스크의 결과: " + result.get());
        } else {
            System.out.println("Callable 태스크의 결과가 아직 준비되지 않았습니다.");
        }
    }
}


주의 사항

  • scheduleAtFixedRate와 scheduleWithFixedDelay 메서드를 사용할 때, 태스크의 실행 시간이 예상보다 길어지는 경우 예기치 않은 동작이 발생할 수 있으므로 주의가 필요합니다.
  • 스케줄러를 더 이상 사용하지 않을 때는 반드시 shutdown() 메서드를 호출하여 스레드 풀을 종료해야 합니다. 그렇지 않으면 프로그램이 종료되지 않는 문제가 발생할 수 있습니다.

ScheduledExecutorService는 정확한 타이밍이나 주기적인 작업 실행이 필요한 애플리케이션에 유용하게 사용될 수 있습니다.

 

Thread Join

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread 1");
        Thread thread2 = new Thread(new MyRunnable(), "Thread 2");

        // Start thread1
        thread1.start();

        try {
            // Wait for thread1 to complete
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Start thread2 after thread1 completes
        thread2.start();
    }
}

class MyRunnable implements Runnable {
    public void run() {
        try {
            // Perform some task
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

'Java' 카테고리의 다른 글

Functional Interface  (0) 2024.04.09
자바 예외 처리  (0) 2024.04.09
Generics 4  (0) 2024.04.09
Jenerics 3  (0) 2024.04.09
Jenerics 2  (0) 2024.04.09