Java 비동기 프로그래밍

2025. 1. 15. 20:00·Java

Thread를 구현하는 2가지 방법

멀티스레딩 프로그래밍에서 사용되는 인터페이스

1. Runnable 인터페이스 구현
2. Callable  인터페이스 구현
- Runnable은 return value가 없기 때문에 ExecutorService에서 비동기로 처리해도 문제가 없었다.
- 하지만 Callable은 return value가 있고, Exception도 발생할 수 있으므로 ExecutorService에서 submit()을 할 때 해당 task가 완료될 때까지 기다리지 않으므로 ExecutorService는 결과를 직접 반환할 수 없다. 그래서 Future라는 특수한 result type을 반환하고, Future를 통해서 나중에 실제 실행 결과를 검색할 수 있다.



 Runnable & Callable Interface Code


1. Runnable 

   public interface Runnable {
       public abstract void run();  
    }


2. Callable 
   - 인자를 받지 않고, 특정 타입의 객체를 리턴한다.
   - call() 수행 중 Exception을 발생시킬 수 있다.

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

   - 수행 코드

import java.util.concurrent.*;

public class CallableAsyncExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("메인 스레드 시작");

        Callable<String> task = () -> {
            Thread.sleep(2000);
            return "비동기 작업 완료!";
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(task); // 비동기 실행 및 결과 대기 가능

        System.out.println("메인 스레드에서 다른 작업 수행 중...");

        // 결과를 기다릴 필요가 없으나, 필요할 때 get() 호출
        String result = future.get(); // 결과를 기다림 (블로킹)
        System.out.println("결과: " + result);

        executor.shutdown();
    }
}

 

ExecutorService에서 Callable 사용 방법

- ExecutorService에서 Callable을 Job으로 등록하고 수행시킬 수 있다.

 

CompletableFuture

  • 비동기 작업 수행: 메인 스레드와 별도로 작업을 수행할 수 있다.
  • 결과 반환 가능: 작업 완료 후 값을 반환할 수 있다.
  • 체이닝 지원: thenApply, thenAccept 등을 사용하여 연속적인 작업 수행 가능.
  • 예외 처리 가능: exceptionally, handle 등을 통해 예외를 쉽게 처리할 수 있다.
  • 논블로킹(non-blocking): Future와 달리 get() 호출 없이도 작업을 연결할 수 있다.

 

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        System.out.println("메인 스레드 시작");

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000); // 비동기 작업
                System.out.println("비동기 작업 완료");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("메인 스레드에서 다른 작업 수행 중...");
        
        // 결과를 기다리지 않고 비동기 작업 진행
        future.join(); // (옵션) 메인 스레드 종료 전 비동기 작업 완료 대기
        System.out.println("메인 스레드 종료");
    }
}

//결과
메인 스레드 시작
메인 스레드에서 다른 작업 수행 중...
비동기 작업 완료
메인 스레드 종료

 

🎯 정리: Runnable vs Callable vs CompletableFuture

특징 Runnable Callable CompletableFuture
비동기 지원 여부 지원 가능 (결과 없음) 지원 가능 (결과 있음) 완전 비동기 지원 (결과 있음)
결과 반환 여부 없음 있음 있음
예외 처리 Checked Exception 미지원 Checked Exception 지원 Checked Exception 지원
스레드 풀 관리 수동 (Thread) ExecutorService 사용 ExecutorService 사용
Java 버전 1.0 이상 1.5 이상 8 이상

 


ExecutorService란?

  • ExecutorService는 멀티스레딩을 간편하게 다룰 수 있게 해주는 java.util.concurrent 패키지에서 제공하는 스레드 풀 관리를 위한  API. 
  • 스레드 풀 관리로 성능 최적화 가능.
  • 비동기 작업 수행 및 결과 반환을 Runnable, Callable과 함께 지원.
  • 적절한 스레드 풀 선택이 중요 (Fixed, Cached, SingleThread, Scheduled).

ExecutorService는 멀티스레딩의 복잡성을 줄이고, 효율적인 스레드 관리를 제공한다.

✅ 기본 개념

  • 스레드 풀 관리: 여러 스레드를 미리 생성해두고 필요할 때 재사용하는 방식.
  • 비동기 작업 지원: 여러 작업을 동시에 수행 가능.
  • 스레드 수 제한: 스레드 수를 제한하여 과도한 스레드 생성 방지.
  • 작업 제출 및 실행: Runnable, Callable을 실행하고, Future를 반환할 수 있음.
  • 자동 종료 지원 안함: shutdown()을 호출해야 스레드 종료.

 

📌 기본 사용법

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

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // 고정 스레드 풀 생성 (스레드 3개)
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 작업 제출
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("작업 " + taskId + " 실행, 스레드: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 1초 대기
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 스레드 풀 종료 (더 이상 작업을 받지 않음)
        executor.shutdown();
    }
}

//출력결과 (실행 순서는 비동기이므로 다를 수 있음)
작업 1 실행, 스레드: pool-1-thread-1
작업 2 실행, 스레드: pool-1-thread-2
작업 3 실행, 스레드: pool-1-thread-3
작업 4 실행, 스레드: pool-1-thread-1
작업 5 실행, 스레드: pool-1-thread-2

 

📌 스레드 풀 종류

자바의 Executors 클래스는 다양한 ExecutorService를 제공합니다.

1. 고정 크기 스레드 풀 (newFixedThreadPool)

ExecutorService executor = Executors.newFixedThreadPool(3);
 
  • 지정한 수의 스레드만 생성하고, 초과 작업은 대기 상태로 유지.
  • CPU 코어 수에 맞춰 사용하기 적합.

2. 캐시된 스레드 풀 (newCachedThreadPool)

ExecutorService executor = Executors.newCachedThreadPool();
  • 필요할 때 새로운 스레드를 생성하고, 유휴 상태의 스레드는 재사용.
  • 많은 단기 작업에 적합.

3. 단일 스레드 풀 (newSingleThreadExecutor)

ExecutorService executor = Executors.newSingleThreadExecutor();
  • 단일 스레드만 사용.
  • 작업을 순차적으로 실행.

4. 스케줄링된 스레드 풀 (newScheduledThreadPool)

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

        // 3초 후에 실행
        scheduler.schedule(() -> System.out.println("3초 후 실행!"), 3, TimeUnit.SECONDS);

        // 1초 간격으로 반복 실행 (5초 후 시작)
        scheduler.scheduleAtFixedRate(() -> System.out.println("1초 간격 실행!"), 5, 1, TimeUnit.SECONDS);
    }
}

 

📌 작업 제출 방식

ExecutorService는 Runnable, Callable을 사용해 작업을 제출할 수 있습니다.

1. Runnable (결과 없음)

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

2. Callable (결과 반환)

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

Callable<String> task = () -> {
    Thread.sleep(1000);
    return "Callable 작업 완료";
};
Future<String> result = executor.submit(task);
System.out.println("결과: " + result.get()); // 결과 대기

📌 스레드 풀 종료 (shutdown() vs shutdownNow() 차이점)

1. shutdown()

  • 새로운 작업을 받지 않고, 진행 중인 작업은 완료.
 
executor.shutdown();

2. shutdownNow()

  • 현재 진행 중인 작업을 즉시 중단하고, 대기 중인 작업은 취소.
 
executor.shutdownNow();

3. awaitTermination()

  • 특정 시간 동안 기존 작업 완료 대기.
 
executor.awaitTermination(5, TimeUnit.SECONDS);

 

 

참고자료

https://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-examples/
https://codechacha.com/ko/java-callable-vs-runnable/

https://www.geeksforgeeks.org/what-is-java-executor-framework/

ExecutorService - chatGPT

저작자표시 비영리 변경금지 (새창열림)

'Java' 카테고리의 다른 글

Java record  (0) 2025.01.07
Java Secure coding  (0) 2023.04.17
[프로젝트]exception 을 static으로 만든 이유  (0) 2023.04.07
필요없는 검사 예외 사용은 피하라  (0) 2023.01.15
Java 제네릭  (0) 2022.11.28
'Java' 카테고리의 다른 글
  • Java record
  • Java Secure coding
  • [프로젝트]exception 을 static으로 만든 이유
  • 필요없는 검사 예외 사용은 피하라
devstep
devstep
웹 백엔드 개발자
  • devstep
    개발 여정
    devstep
  • 전체
    오늘
    어제
    • 분류 전체보기 (89) N
      • Java (24)
      • Spring Framework (16) N
        • Spring (13) N
        • JPA (3)
      • Database (8)
        • RDBMS공통 (1)
        • MySQL (6)
        • Redis (0)
        • Oracle (1)
      • Concept (13)
        • 테스트코드 (4)
        • 클린코드 (2)
        • 성능테스트 (4)
        • 설계 (1)
        • 인증 (1)
        • REST API (1)
      • git (2)
      • Intellij (4)
      • Computer Science (3)
        • 네트워크 (1)
        • 자료구조 (1)
        • 보안 (1)
      • Essay (17) N
        • Learning Essay (10)
        • WIL (7) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    nginx
    부하테스트
    storageEngine
    tdd
    보안
    단위테스트
    applicationcontext
    테스트코드
    JavaMemoryModel
    성능테스트
    JVM
    aggregate
    자바메모리모델
    대칭암호화
    linux
    인텔리제이실행에러
    springsecurity
    component
    비대칭암호화
    innodb
    nofile
    블록암호화
    seed
    부하테스트도구
    클린코드
    DDD
    JMeter
    ClusteredIndex
    bean
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devstep
Java 비동기 프로그래밍
상단으로

티스토리툴바