멀티 스레드 환경을 지원하기 위해 사용
1. Runnable 인터페이스
특징
- Runnable 인터페이스는 Java 1.0부터 존재하는 기본적인 인터페이스로, 단일 메서드 run()을 제공합니다.
- 반환값이 없으며, 예외를 던질 수 없습니다.
@FunctionalInterface
public interface Runnable {
void run();
}
public class RunnableExample {
public static void main(String[] args) {
// Runnable 구현체 생성
Runnable runnableTask = () -> {
System.out.println("Runnable Task is running...");
};
// 스레드에 Runnable 전달하여 실행
Thread thread = new Thread(runnableTask);
thread.start();
}
}
장점
- 간단한 구조로, 스레드에서 수행할 작업을 정의하기 쉽습니다.
- 예외를 명시적으로 처리할 필요 없이 간단하게 작업을 정의할 수 있습니다.
단점
- 반환값을 제공하지 않으므로, 작업 수행 결과를 받을 수 없습니다.
- run() 메서드는 체크된 예외(Checked Exception)를 던질 수 없으므로, 예외 처리가 필요한 경우 내부적으로 처리해야 합니다.
2. Callable 인터페이스
특징
- Callable 인터페이스는 Java 5에서 java.util.concurrent 패키지와 함께 도입된 인터페이스로, 단일 메서드 call()을 제공합니다.
- 반환값을 가지며, 예외를 던질 수 있습니다.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
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) {
// Callable 구현체 생성
Callable<String> callableTask = () -> {
return "Callable Task Completed";
};
// ExecutorService를 사용하여 Callable 실행
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(callableTask);
try {
// 작업 완료 후 결과 가져오기
String result = future.get();
System.out.println("Callable Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
장점
- call() 메서드는 작업 수행 후 결과를 반환할 수 있습니다.
- 체크된 예외를 던질 수 있어, 예외 처리가 더 유연합니다.
- Future와 같은 API를 통해 비동기 작업의 결과를 받을 수 있습니다.
단점
- Runnable에 비해 구조가 약간 복잡하며, ExecutorService와 Future 같은 추가적인 클래스와 함께 사용해야 하는 경우가 많습니다.
언제 Runnable과 Callable을 사용해야 하는가?
- Runnable을 사용할 때:
- 작업 수행의 결과가 필요 없고, 단순히 백그라운드 작업이나 이벤트 처리를 할 때 적합합니다.
- 예외를 명시적으로 처리할 필요가 없을 때 사용합니다.
- 예: 이벤트 핸들링, 타이머 작업, 단순 스레드 실행.
- Callable을 사용할 때:
- 작업 수행의 결과를 반환해야 할 때 적합합니다.
- 작업 중 예외 처리가 필요하고, 호출한 쪽에서 이를 확인할 필요가 있을 때 사용합니다.
- 예: 데이터베이스 쿼리, 복잡한 계산 작업, 외부 시스템 호출.
- Runnable과 Callable 모두 ExecutorService를 통해 실행할 수 있습니다. Runnable을 사용하면 결과가 없는 Future<?>를 반환하고, Callable을 사용하면 작업 결과를 담은 Future<V>를 반환합니다.
ExecutorService executor = Executors.newFixedThreadPool(2);
// Runnable 예제
Runnable runnableTask = () -> System.out.println("Runnable Task Running");
Future<?> runnableFuture = executor.submit(runnableTask); // 결과가 없음
// Callable 예제
Callable<Integer> callableTask = () -> {
return 123;
};
Future<Integer> callableFuture = executor.submit(callableTask); // 결과가 있음
executor.shutdown();
executor service.submit는 멀티스레드 시작!!
ExecutorService 종료 관련
ExecutorService는 작업을 스레드 풀에서 관리하고 실행하는 인터페이스로, 사용이 끝나면 반드시 종료(shutdown)해줘야 합니다. 그렇지 않으면 애플리케이션이 종료되지 않고 백그라운드에서 스레드가 계속 실행될 수 있습니다.
ExecutorService의 종료 필요성
ExecutorService는 기본적으로 백그라운드 스레드 풀을 관리합니다. 따라서 다음과 같은 이유로 사용이 끝난 후 반드시 종료해야 합니다:
- 리소스 해제:
- 스레드 풀에 의해 사용되는 스레드와 기타 리소스를 해제하여 메모리 누수를 방지합니다.
- 정상적인 애플리케이션 종료:
- 스레드 풀이 종료되지 않으면 JVM이 종료되지 않고 계속 대기 상태에 있을 수 있습니다.
- 명시적 종료 호출:
- executor.shutdown()을 호출하여 스레드 풀을 정상적으로 종료합니다. 이 메서드는 더 이상 새로운 작업을 수락하지 않고, 기존에 제출된 작업이 완료될 때까지 기다립니다.
- executor.shutdownNow()를 호출하면 모든 작업을 중지하고, 실행 중인 작업을 즉시 종료하려고 시도합니다.
- ExecutorService는 AutoCloseable을 구현하지 않기 때문에 try-with-resources 구문을 직접 사용할 수 없습니다.
public class ExecutorServiceExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2"));
} finally {
// ExecutorService 종료
executor.shutdown();
try {
if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}
위 코드에서는 shutdown() 메서드를 호출하여 새로운 작업을 수락하지 않도록 하고, awaitTermination()을 사용하여 스레드 풀이 완전히 종료될 때까지 대기합니다. awaitTermination()은 주어진 시간 동안 스레드 풀이 종료될 때까지 기다리며, 그 시간이 지나도 종료되지 않으면 shutdownNow()를 호출하여 강제로 종료를 시도합니다.
'개발 > java' 카테고리의 다른 글
자바 버전별 특징 (0) | 2024.09.23 |
---|---|
[java8+] 함수형 프로그래밍 @FunctionalInterface (0) | 2024.09.21 |
[힙 덤프] 떠있는 프로세스 분석 (0) | 2024.09.19 |
불변객체 만들기 ImmutableCollections.class (0) | 2024.09.14 |
[java] try with resources; close (1) | 2024.05.09 |