Spring Security / / 2024. 10. 29. 23:46

Callable과 Runnable

CallableRunnable은 Java에서 멀티스레딩을 구현할 때 사용하는 두 가지 인터페이스로, 각각의 차이점과 활용법이 있습니다. 특히, 비동기 작업병렬 처리가 필요한 상황에서 이 두 인터페이스를 통해 간단하고 효율적인 스레드 작업을 구현할 수 있습니다.

 

아래에 CallableRunnable을 자세히 설명하겠습니다.

 

1. Runnable

 

Runnable은 Java 1.0부터 제공된 인터페이스로, 스레드에서 수행할 작업을 정의합니다. Runnable 인터페이스는 리턴값이 없으며 단순히 스레드를 실행할 때 필요한 로직을 정의할 수 있습니다.

 

Runnable 기본 구조

 

Runnable 인터페이스는 단 하나의 메서드, run()을 가집니다.

@FunctionalInterface
public interface Runnable {
    void run();
}

 

run() 메서드: 스레드에서 실행할 작업을 구현하는 메서드로, 리턴값이 없습니다.

 

Runnable 사용 예시

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable 작업 실행 중...");
    }
}

// Runnable 사용
public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

 

주요 특징

 

리턴값이 없으며, 예외 처리도 메서드 내에서 직접해야 합니다.

단순한 작업(리턴값 불필요)에 적합하며, 결과값이 필요하지 않은 비동기 작업에 주로 사용됩니다.

Thread 객체의 생성자에 직접 넘겨서 사용합니다.

 

2. Callable

 

Callable은 Java 1.5에서 java.util.concurrent 패키지와 함께 도입된 인터페이스로, 결과값을 반환할 수 있고 예외 처리가 가능한 비동기 작업을 수행할 때 사용됩니다.

 

Callable 기본 구조

 

Callable 인터페이스는 제네릭 타입을 사용하여 반환할 값의 타입을 지정할 수 있으며, call() 메서드를 가집니다.

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

 

call() 메서드: 작업을 수행하고, 결과를 리턴하며 예외 처리가 가능합니다.

 

Callable 사용 예시

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Callable 작업 완료";
    }
}

// Callable 사용
public class Main {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> result = executor.submit(new MyCallable());

        // 결과값 가져오기
        System.out.println("결과: " + result.get());

        executor.shutdown();
    }
}

 

주요 특징

 

리턴값이 있으며, Future 객체를 통해 작업 완료 후 결과를 받을 수 있습니다.

예외 처리가 가능합니다. call() 메서드에 예외를 던질 수 있습니다.

비동기 작업에서 결과값을 얻거나 예외 처리가 필요한 경우에 적합합니다.

 

3. Runnable과 Callable의 비교

비교 항목 Runnable Callable
도입 시기 Java 1.0 Java 1.5 (java.util.concurrent)
메서드 run() call()
리턴 타입 void (리턴값 없음) 제네릭 타입 (리턴값 있음)
예외 처리 run() 메서드 내에서 처리해야 함 call() 메서드에서 예외를 던질 수 있음
주요 용도 간단한 비동기 작업 (리턴값 필요 없음) 비동기 작업 + 결과값/예외 처리 필요할 때
사용 방법 Thread 클래스의 생성자에서 사용 ExecutorService.submit() 메서드로 사용

 

4. Runnable과 Callable의 실제 사용 예

 

Java에서는 ExecutorService를 활용하여 RunnableCallable을 함께 사용하여 다양한 비동기 작업을 수행할 수 있습니다. 아래는 두 인터페이스를 활용한 실제 예입니다.

 

Runnable 예제 (간단한 비동기 작업)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RunnableExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Runnable task = () -> System.out.println("Runnable 실행 중...");
        executor.submit(task);

        executor.shutdown();
    }
}

 

Callable 예제 (결과값이 필요한 작업)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Callable<String> task = () -> {
            Thread.sleep(1000);
            return "Callable 실행 완료";
        };

        Future<String> result = executor.submit(task);
        System.out.println("결과값: " + result.get()); // 작업 완료 후 결과값 가져오기

        executor.shutdown();
    }
}

 

5. Runnable과 Callable의 활용 시 주의사항

 

Runnable에서 결과값이 필요한 경우: Runnable 인터페이스는 결과를 리턴하지 않기 때문에, 결과가 필요하면 전역 변수를 사용하거나 별도로 FutureTask와 같은 클래스를 사용하는 것이 좋습니다.

Callable의 결과값 지연: Callable의 결과는 Future.get() 메서드를 호출하기 전까지 기다립니다. get() 호출 시, 작업이 완료될 때까지 블로킹되므로 성능에 주의해야 합니다.

예외 처리: Callable은 예외 처리가 가능하지만, Runnable은 메서드 내부에서 예외를 처리해야 하므로 필요에 따라 Callable을 선택하는 것이 좋습니다.

 

6. Runnable과 Callable을 혼합하여 사용하기

 

때로는 두 인터페이스를 함께 사용하여 단순 작업복잡한 작업을 각각 처리하는 경우가 있습니다. 예를 들어, 여러 Runnable 작업을 실행하면서, 특정한 결과가 필요한 경우에만 Callable을 사용하는 방식입니다.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class MixedExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // Runnable 작업 리스트
        List<Runnable> runnableTasks = List.of(
                () -> System.out.println("Runnable Task 1"),
                () -> System.out.println("Runnable Task 2")
        );

        // Callable 작업 리스트
        List<Callable<String>> callableTasks = new ArrayList<>();
        callableTasks.add(() -> "Callable Task Result 1");
        callableTasks.add(() -> "Callable Task Result 2");

        // Runnable 실행
        for (Runnable task : runnableTasks) {
            executor.submit(task);
        }

        // Callable 실행 및 결과 확인
        for (Callable<String> task : callableTasks) {
            Future<String> result = executor.submit(task);
            System.out.println("Callable 결과: " + result.get());
        }

        executor.shutdown();
    }
}

 

설명

 

Runnable 작업은 실행되지만 결과값을 리턴하지 않습니다.

Callable 작업은 결과값을 리턴하며, Future.get()으로 결과를 확인할 수 있습니다.

 

결론

 

RunnableCallable은 각각 간단한 비동기 작업결과값이 필요한 작업에 유용하게 사용됩니다. Runnable은 단순히 실행만 하면 되는 작업에 적합하고, Callable은 결과값을 반환하거나 예외를 처리해야 할 때 활용하기 좋습니다.

 

두 인터페이스의 특징을 이해하고, 필요한 경우에 적합한 인터페이스를 선택하여 효율적인 비동기 작업을 구성할 수 있습니다.

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유