backend
  • README
  • DOCS
    • Java Docs
    • Servlet Docs
    • JSP Docs
    • DB & SQL Docs
    • Spring Boot Docs
    • Spring Security Docs
    • AWS Docs
  • 설치하기
    • Intellij 설정
  • 자바
    • 01 Java란?
    • 02 자바 시작하기
    • 03 자료형과 연산자
    • 04 제어문
    • 05 메소드
    • 06 클래스 기초
      • Static 보충자료
      • 패키지 보충자료
    • 07 객체지향 프로그래밍
    • 08 클래스 더 알아보기
      • 열거형 ENUM 보충자료
    • 09 클래스와 자료형
      • 다형성 보충자료
      • 제네릭 보충자료
    • 10 컬렉션 프레임워크
      • 컬렉션 프레임워크 보충자료
    • 11 람다식과 함수형 프로그래밍
      • 람다식 보충자료
    • 12 오류 대비하기
      • 오류 보충자료
    • 13 멀티태스킹
      • 멀티태스킹 보충자료
    • 교재보충
      • java.lang
  • 스프링
    • 서블릿, JSP
      • 05 Servlet(서블릿)
        • 서블릿 보충자료
        • 서블릿 추가코드
        • XML, YAML, JSON
      • 06 JSP(자바 서버 페이지)
        • JSP 보충자료
      • 07 JSTL(JSP 스탠다드 태그 라이브러리)
        • JSTL 보충자료
      • 08 Cookie(쿠키), Session(세션)
      • 09 서블릿,필터,리스너
        • 서블릿,필터,리스너 보충자료
      • 11 도서관리 프로젝트 실습
    • Spring Boot
      • 01 스프링 등장 배경, 객체지향
        • 스프링 등장 배경, 객체지향 보충자료
      • 02 IOC(제어의 역전), DI(의존성 주입)
        • IOC 보충자료
        • DI 보충자료
      • 03 스프링 구조
        • 스프링 구조 보충설명
      • 04 테스트코드 실습
      • 05 스프링 빈 설정
        • 스프링 빈 설정 보충자료
      • 06 싱글톤
        • 싱글톤 보충 자료
      • 07 스프링 빈 자동설정
        • 스프링 빈 자동설정 보충자료
      • 08 빈 생명주기
        • 빈 생명주기 보충자료
      • 09 빈 스코프
        • 빈 스코프 보충자료
      • 10 스프링 MVC
        • 스프링 MVC 보충자료
        • 데이터베이스 연동에 필요한 부분
      • 11 Validation(검증)
        • Validation(검증) 보충자료
      • 12 Bean Validation(빈검증)
        • Bean Validation(빈검증) 보충자료
      • 13 예외처리
        • 예외처리 보충자료
      • 14 타입변환
      • 15 JDBC(Java Database Connectivity)
      • 16 커넥션풀
      • 17 트랜잭션
        • 트랜잭션 보충자료
      • 18 JDBC 템플릿 활용
      • 19 MyBatis
      • 20 JPA(Java Persistence API)
      • 22 게시판 프로젝트 실습
    • Spring Security
      • 보안(Security)
      • Spring Security
      • 2. Spring Security 알아보기
        • 보안 위협 실제 사례와 방어 전략
      • 3. Spring Security 기본 동작 흐름
      • 4. Spring Security로 인증 권한 추가하기
        • Spring Security의 인증 및 인가
      • 5. Spring Security에서 세션 관리하기
        • 세션(Session)과 쿠키(Cookie) 비교, 토큰(Token)과의 관계
        • 해싱 및 해싱알고리즘
        • base64
      • 6. Spring Security 악용 보호
        • SameSite
      • 7. Spring Security로 인가 권한 추가하기
      • 8. Bcrypt(비크립트) 암호화
      • OAuth2 적용하기
  • 네트워크
    • HTTP
    • OSI 7계층
  • DB&SQL
    • 01 Database(데이터베이스)와 SQL 개요
    • 02 관계형 모델
    • 03 집합
    • 04 JOIN 연산
    • 05 MySQL
      • 세이브포인트
      • DBeaver, Mysql 오토커밋 설정 관련
    • 06 SQL 기초
      • 예시데이터 쿼리문
    • 07 SQL 실습
      • 실습 스키마
    • 08 Join 활용
      • 실습스키마
    • 09 SQL 활용
      • 실습스키마
    • 10 정규화
      • 실습 스키마
    • 데이터타입
    • 예시 프로젝트 스키마 구성
  • AWS
    • SSL 연결하기
    • 보충설명
Powered by GitBook
On this page
  • 프로세스와 스레드
  • 프로세스 (Process)
  • 스레드 (Thread)
  • 프로세스와 스레드의 비교
  • 4. 프로세스와 스레드의 관계
  • 자바에서의 스레드
  • 스레드 생성 예시
  • Runnable 인터페이스를 사용한 스레드 생성
  • 스레드 생성 및 sleep 사용
  • 출력 결과
  • 다중 스레드에서의 사용 예시
  • 출력 결과 (예시)
  • 주의사항
  • 스레드 다루기
  • 스레드 이름 설정하기
  • 1. setName() 메소드
  • 2. 스레드 이름 가져오기
  • 3. 스레드 이름의 기본값
  • 4. .join() 메소드
  • 스레드를 사용한 멀티태스킹
  • 1. 멀티태스킹 구현 예시
  • 예제 : 멀티태스킹 구현
  • 멀티태스킹의 장점과 주의사항
  • 스레드 그룹과 데몬 스레드
  • 스레드 그룹(Thread Group)
  • 1. 스레드 그룹 생성 및 사용 예시
  • 출력 예시
  • 코드 설명
  • 스레드 그룹의 주요 메소드
  • 데몬 스레드(Daemon Thread)
  • 데몬 스레드의 특징
  • 데몬 스레드 설정 예시
  • 출력 예시:
  • 코드 설명
  • 동기화**(Synchronization)**
  • 동기화(Synchronization)란?
  • 1. 자바에서의 동기화 구현 방법
  • 1.1 synchronized 메소드
  • 1.2 synchronized 블록
  • 1.3 synchronized 블록을 사용하여 특정 객체 동기화
  • 2. 동기화의 중요성: 동기화 문제 예시
  • 동기화 없이 동작하는 코드 예시
  • 출력 예시
  • 3. 동기화된 코드로 문제 해결
  • 출력 예시
  • 4. 동기화의 영향
  • 5. 정리
  • 스레드 풀
  • 스레드 풀(Thread Pool)이란?
  • 1. 스레드 풀의 필요성
  • 2. 자바에서 스레드 풀 사용: Executor 프레임워크
  • 2.1. Executors 클래스로 스레드 풀 생성하기
  • 3. 스레드 풀 사용 예제
  • 출력 예시
  • 코드 설명
  • 4. 스레드 풀의 주요 메소드
  • 5. 주의사항
  • 6. 정리
  • Callable의 개념
  • 1. Callable과 Runnable의 차이점
  • 2. Callable 사용 예시
  • 코드 설명
  • 3. Callable의 이점
  • Future 개념
  • 1. Future의 주요 메소드
  • 2. Future의 사용 예시
  • 출력 예시
  • 코드 설명
  • CompletableFuture란?
  • 1. CompletableFuture의 주요 특징
  • 2. CompletableFuture의 생성 및 비동기 실행
  • 2.1. CompletableFuture 생성 및 사용 예시
  • 출력 예시
  • 코드 설명
  • 3. 다양한 비동기 작업 실행 방법
  • 4. 후속 작업 및 콜백 처리
  • 4.1. 작업 완료 후 콜백 처리
  • 출력 예시
  • 5. 여러 작업의 조합
  • 5.1. 두 작업 조합
  • 출력 예시
  • 5.2. 모든 작업 완료 후 처리
  • 6. 예외 처리
  • 출력 예시
  • 7. CompletableFuture의 장점
  • 8. 정리
  1. 자바

13 멀티태스킹

프로세스와 스레드

프로세스 (Process)

  • 정의: 프로세스는 실행 중인 프로그램의 인스턴스로, 프로그램이 메모리에서 실행되고 있는 독립된 작업 단위입니다. 운영체제는 각 프로세스에 고유한 메모리 공간과 시스템 자원을 할당합니다. 프로세스는 여러개의 스레드를 포함할 수 있는 단위라고 할 수 있습니다.

  • 특징:

    • 독립된 메모리: 각 프로세스는 운영체제로부터 독립된 메모리 공간(코드, 데이터, 힙, 스택)을 할당받습니다. 따라서 한 프로세스의 메모리에 다른 프로세스가 직접 접근할 수 없습니다.

    • 자원 할당: 프로세스는 실행에 필요한 CPU 시간, 메모리, 파일 핸들, 네트워크 연결 등의 자원을 할당받습니다.

    • 멀티프로세싱: 하나의 운영체제에서 여러 프로세스가 동시에 실행되는 것을 멀티프로세싱이라고 합니다.

    • 무거운 작업 단위: 프로세스 간의 통신은 IPC(Inter-Process Communication) 메커니즘을 사용하며, 컨텍스트 스위칭 비용이 큽니다.

  • 예시:

    • 우리가 컴퓨터에서 실행하는 각 프로그램(예: 웹 브라우저, 텍스트 편집기)은 각각 독립된 프로세스입니다.

    • 프로세스 간에는 메모리 공간이 분리되어 있어서 서로 간섭 없이 실행됩니다.

스레드 (Thread)

  • 정의: 스레드는 프로세스 내에서 실행되는 작업의 최소 단위입니다. 하나의 프로세스는 여러 스레드를 포함할 수 있으며, 프로세스의 메모리 공간을 공유하면서 작업을 수행합니다.

  • 특징:

    • 공유 메모리: 같은 프로세스 내에서 여러 스레드는 프로세스의 코드, 데이터, 힙을 공유합니다. 그러나 각 스레드는 고유한 스택과 레지스터를 가지고 있습니다.

    • 가벼운 작업 단위: 스레드 간의 통신은 메모리 공간을 공유하기 때문에 프로세스 간 통신보다 훨씬 빠르고 효율적입니다.

    • 멀티스레딩: 하나의 프로세스에서 여러 스레드가 동시에 실행되는 것을 멀티스레딩이라고 합니다. 스레드를 사용하면 프로세스가 동시에 여러 작업을 수행할 수 있습니다.

    • 컨텍스트 스위칭: 스레드 간의 컨텍스트 스위칭은 프로세스 간의 컨텍스트 스위칭보다 비용이 적습니다.

  • 예시:

    • 웹 브라우저는 여러 탭을 열어 각각의 웹 페이지를 로드하고, 이때 각 탭은 스레드로 실행될 수 있습니다.

    • 게임에서는 그래픽 렌더링, 사용자 입력 처리, 네트워크 통신 등이 각각 별도의 스레드로 실행될 수 있습니다.

프로세스와 스레드의 비교

특성

프로세스(Process)

스레드(Thread)

메모리

독립된 메모리 공간(코드, 데이터, 힙, 스택)

프로세스 내에서 메모리(코드, 데이터, 힙)를 공유하고 스택은 별도

통신

프로세스 간 통신은 IPC(파이프, 소켓 등) 사용

공유 메모리 공간을 사용하여 통신이 용이

컨텍스트 스위칭

비용이 높음

비용이 상대적으로 낮음

자원 할당

운영체제가 개별적으로 자원을 할당

스레드는 프로세스의 자원을 공유

독립성

서로 독립적이며 한 프로세스의 오류가 다른 프로세스에 영향을 주지 않음

같은 프로세스 내에서는 서로 영향을 줄 수 있음

4. 프로세스와 스레드의 관계

  • 프로세스는 하나 이상의 스레드를 포함할 수 있으며, 최소한 하나의 스레드(main thread)를 가지고 있습니다.

  • 여러 스레드를 생성하여 프로세스 내의 자원을 공유하며 작업을 수행할 수 있습니다. 이를 통해 멀티스레딩을 활용하여 프로그램의 성능을 향상시킬 수 있습니다.

자바에서의 스레드

자바에서는 멀티스레딩을 쉽게 구현할 수 있도록 Thread 클래스와 Runnable 인터페이스를 제공합니다. 자바의 스레드는 JVM이 관리하는 프로세스 내에서 실행되며, 다음과 같이 사용할 수 있습니다.

스레드 생성 예시

edu.ch13.thread.basicThread 패키지 - ThreadExample 클래스

// Thread 클래스를 상속하여 스레드 생성
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ": " + i);
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        // 스레드 실행
        thread1.start();
        thread2.start();
    }
}
  • run() 메소드: 각 스레드의 작업을 정의하는 메소드입니다. for 루프를 사용하여 각 작업이 다섯 번 반복되도록 구현했습니다.

  • start() 메소드: 스레드를 시작하기 위해 start() 메소드를 호출합니다. 이 메소드는 내부적으로 run() 메소드를 실행하며, 스레드를 병렬로 실행합니다.

Runnable 인터페이스를 사용한 스레드 생성

edu.ch13.thread 패키지 - RunnableExample 클래스

// Runnable 인터페이스를 구현하여 스레드 생성
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());

        // 스레드 실행
        thread1.start();
        thread2.start();
    }
}

Runnable과 Thread의 차이

항목

Runnable

Thread

상속 여부

다른 클래스를 상속받을 수 있음

이미 Thread를 상속받았으므로 다른 상속 불가

구현 방식

Runnable 인터페이스 구현

Thread 클래스를 직접 상속

유연성

더 유연한 설계 가능

덜 유연 (다중 상속 불가)

실행 방식

Thread 객체에 전달하여 실행

start() 메서드로 직접 실행

스레드 생성 및 sleep 사용

sleep 메소드를 사용하여 스레드가 1초(1000밀리초) 동안 잠드는 예시를 보여줍니다:

edu.ch13.thread 패키지

public class SleepExample {
    public static void main(String[] args) {
        System.out.println("작업 시작");

        try {
            // 현재 스레드를 2초(2000밀리초) 동안 일시 중지
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            System.out.println("스레드가 인터럽트되었습니다.");
        }

        System.out.println("작업 종료");
    }
}

출력 결과

작업 시작
(2초간 대기)
작업 종료

위 코드는 main 스레드가 "작업 시작" 메시지를 출력한 후 Thread.sleep(2000)에 의해 2초 동안 일시 중지된 다음 "작업 종료" 메시지를 출력합니다.

다중 스레드에서의 사용 예시

sleep 메소드는 멀티스레드 환경에서 특정 스레드의 실행을 일시 중지할 때 유용합니다. 다음은 두 개의 스레드가 sleep을 사용하여 번갈아가며 실행되는 예시입니다:

edu.ch13.thread 패키지 - MultiThreadSleepExample 클래스

class MyThread extends Thread {
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(name + ": " + i);
            try {
                // 1초 동안 스레드를 일시 중지
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(name + " 스레드가 인터럽트되었습니다.");
            }
        }
    }
}

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

        thread1.start();
        thread2.start();
    }
}

출력 결과 (예시)

실행할 때마다 결과가 다를 수 있습니다. 일반적으로 두 스레드가 1초 간격으로 번갈아 실행됩니다.

Thread 2: 1
Thread 1: 1
Thread 2: 2
Thread 1: 2
Thread 1: 3
Thread 2: 3
Thread 1: 4
Thread 2: 4
Thread 1: 5
Thread 2: 5

주의사항

현재 실행 중인 스레드만 일시 중지됩니다: Thread.sleep()은 호출한 스레드에만 적용되며, 다른 스레드를 일시 중지시키지 않습니다. 예시로 메인 인스턴스에서 호출하게 되면 다른 스레드에서 적용되는 것이 아닌 메인 인스턴스만 일시 중지 됩니다.

스레드 다루기

스레드 이름 설정하기

1. setName() 메소드

스레드 객체를 생성한 후에 setName() 메소드를 사용하여 스레드의 이름을 설정할 수 있습니다.

edu.ch13.thread 패키지

public class ThreadSetNameExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 스레드 이름 설정
        thread1.setName("Worker-1");
        thread2.setName("Worker-2");

        // 스레드 시작
        thread1.start();
        thread2.start();
    }
}

2. 스레드 이름 가져오기

현재 실행 중인 스레드의 이름을 가져오려면 Thread.currentThread().getName() 메소드를 사용합니다. 위의 예제에서도 이 메소드를 사용하여 스레드의 이름을 출력하고 있습니다.

3. 스레드 이름의 기본값

스레드를 생성할 때 이름을 지정하지 않으면, JVM은 자동으로 "Thread-n" 형식의 이름을 할당합니다. 여기서 n은 스레드가 생성된 순서대로 증가하는 숫자입니다.

4. .join() 메소드

.join() 메소드는 자바의 Thread 클래스에서 제공되는 메소드로, 현재 실행 중인 스레드가 다른 스레드의 종료를 기다리도록 하는 데 사용됩니다. 즉, 한 스레드가 다른 스레드의 작업이 완료될 때까지 일시 중지되어 기다리는 것을 의미합니다.

예를 들어, thread1.join()을 호출하면 현재 스레드는 thread1이 작업을 완료할 때까지 기다리게 됩니다. thread1이 완료되면 현재 스레드는 그 이후의 작업을 계속 진행합니다.

스레드를 사용한 멀티태스킹

멀티태스킹은 한 번에 여러 작업을 수행하는 것을 말하며, 자바에서 스레드를 사용하여 멀티태스킹을 구현할 수 있습니다. 멀티태스킹은 CPU를 효율적으로 활용하여 응용 프로그램의 성능과 응답성을 향상시킵니다. 자바에서 멀티태스킹은 여러 스레드를 생성하고 실행하여 동시에 여러 작업을 수행하는 방식으로 이루어집니다.

다음은 스레드를 사용하여 멀티태스킹을 구현하는 예시와 코드 설명을 제공하겠습니다.

1. 멀티태스킹 구현 예시

아래 예제에서는 두 개의 작업을 수행하는 두 개의 스레드를 생성하고, 각 스레드가 독립적으로 실행되도록 합니다.

예제 : 멀티태스킹 구현

edu.ch13.thread 패키지 - MultiTaskingExample1 클래스

class Task1 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Task 1 - Step " + i + "time: " + i + "sec");
            try {
                // 1초 동안 일시 중지
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Task 1 interrupted");
            }
        }
        System.out.println("Task 1 completed.");
    }
}

class Task2 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Task 2 - Step " + i
                    + "time: " + i * 1.5 + "sec");
            try {
                // 1.5초 동안 일시 중지
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                System.out.println("Task 2 interrupted");
            }
        }
        System.out.println("Task 2 completed.");
    }
}

public class MultiTaskingExample1 {
    public static void main(String[] args) {
        // 스레드 생성
        Task1 task1 = new Task1();
        Task2 task2 = new Task2();

        // 스레드 시작
        task1.start();
        task2.start();
    }
}

sleep() 메소드: 각 스레드의 실행을 잠시 멈추기 위해 Thread.sleep()을 사용하였습니다. sleep 메소드를 통해 스레드 간의 실행 순서를 섞어 멀티태스킹의 효과를 확인할 수 있습니다.

멀티태스킹의 장점과 주의사항

  • 장점:

    • CPU 활용도 향상: 스레드가 동시에 여러 작업을 수행하여 멀티코어 프로세서의 성능을 최대한 활용할 수 있습니다.

    • 응답성 개선: 특히 GUI 애플리케이션이나 서버 애플리케이션에서 멀티스레딩은 응답 시간을 개선하고 사용자 경험을 향상시킵니다.

  • 주의사항:

    • 동기화 문제: 여러 스레드가 동일한 자원에 동시에 접근할 때 데이터 불일치나 경쟁 조건이 발생할 수 있습니다. 이런 경우 동기화(synchronization) 메커니즘을 사용하여 문제를 해결해야 합니다.

    • 자원 소모: 과도한 스레드 생성은 시스템의 메모리 및 CPU 자원을 소모할 수 있으므로, 스레드 풀과 같은 자원 관리 기법을 사용하는 것이 좋습니다.

스레드 그룹과 데몬 스레드

스레드 그룹(Thread Group)

  • 스레드 그룹(Thread Group)은 여러 스레드를 하나의 그룹으로 묶어 관리하는 기능을 제공합니다. 스레드 그룹을 사용하면 스레드의 우선순위 설정, 일괄 시작/중지, 데몬 스레드 설정 등을 그룹 단위로 쉽게 관리할 수 있습니다.

스레드 그룹은 주로 스레드를 논리적으로 분류하거나 관련된 스레드를 함께 제어할 필요가 있을 때 사용됩니다.

1. 스레드 그룹 생성 및 사용 예시

edu.ch13.thread 패키지

public class ThreadGroupExample {
    public static void main(String[] args) {
        // 스레드 그룹 생성
        ThreadGroup group = new ThreadGroup("MyThreadGroup");

        // 스레드 생성 및 스레드 그룹에 추가
        Thread thread1 = new Thread(group, () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()
                        + " 실행 중 " + (i + 1) + "번째");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-1");

        Thread thread2 = new Thread(group, () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()
                        + " 실행 중 " + (i + 1) + "번째");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-2");

        // 스레드 시작
        thread1.start();
        thread2.start();

        // 스레드 그룹 정보 출력
        System.out.println("스레드 그룹 이름: " + group.getName());
        group.list();  // 그룹에 속한 스레드들의 정보 출력
    }
}

출력 예시

스레드 그룹 이름: MyThreadGroup
java.lang.ThreadGroup[name=MyThreadGroup,maxpri=10]
    Thread[Thread-1,5,MyThreadGroup]
    Thread[Thread-2,5,MyThreadGroup]
Thread-2 실행 중 1번째
Thread-1 실행 중 1번째
Thread-2 실행 중 2번째
Thread-1 실행 중 2번째
Thread-2 실행 중 3번째
Thread-1 실행 중 3번째
Thread-1 실행 중 4번째
Thread-2 실행 중 4번째
Thread-2 실행 중 5번째
Thread-1 실행 중 5번째

코드 설명

  • 스레드 그룹 생성: ThreadGroup group = new ThreadGroup("MyThreadGroup");를 통해 새로운 스레드 그룹을 생성합니다.

  • 스레드 그룹에 스레드 추가: new Thread(group, ...) 형식으로 스레드를 생성하면서 스레드 그룹을 지정할 수 있습니다.

  • 스레드 그룹 정보 출력: group.getName()으로 그룹의 이름을 얻을 수 있으며, group.list()로 그룹에 포함된 스레드의 정보를 출력할 수 있습니다.

스레드 그룹의 주요 메소드

  • getName(): 스레드 그룹의 이름을 반환합니다.

  • list(): 그룹 내 모든 스레드와 하위 스레드 그룹의 정보를 출력합니다.

  • activeCount(): 그룹 내 활성화된 스레드의 수를 반환합니다.

  • interrupt(): 그룹 내 모든 스레드를 일괄적으로 중단(interrupt)할 수 있습니다.

데몬 스레드(Daemon Thread)

  • 데몬 스레드(Daemon Thread)는 백그라운드에서 실행되는 스레드로, 주 스레드가 종료되면 함께 종료되는 특성을 가지고 있습니다. 일반적으로 데몬 스레드는 JVM의 작업을 보조하는 역할을 하며, 예를 들어 가비지 컬렉터, 로그 기록, 파일 청소 등의 작업을 수행합니다.

데몬 스레드의 특징

  • 백그라운드 작업: 데몬 스레드는 메인 스레드와 같은 주요 작업을 수행하지 않으며, 주로 백그라운드에서 보조적인 작업을 수행합니다.

  • 주 스레드 종료 시 자동 종료: 모든 일반 스레드(Non-Daemon Thread)가 종료되면 데몬 스레드는 자동으로 종료됩니다.

  • 설정 방법: 스레드의 setDaemon(true) 메소드를 호출하여 데몬 스레드로 설정할 수 있습니다. 이 메소드는 스레드가 시작되기 전에만 호출할 수 있습니다.

데몬 스레드 설정 예시

edu.ch13.thread 패키지

public class DaemonThreadExample {
    public static void main(String[] args) {
        // 데몬 스레드 생성
        Thread daemonThread = new Thread(() -> {
            try {
                while (true) {
                    System.out.println("데몬 스레드 실행 중...");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                System.out.println("데몬 스레드 종료");
            }
        });

        // 데몬 스레드로 설정
        daemonThread.setDaemon(true);

        // 데몬 스레드 시작
        daemonThread.start();

        // 메인 스레드 작업
        System.out.println("메인 스레드 실행 중...");

        // 새로운 스레드 생성: 3초 후 메인 스레드를 인터럽트
        Thread interruptingThread = new Thread(() -> {
            try {
                Thread.sleep(3000); // 3초 대기
                System.out.println("메인 스레드 인터럽트 시도...");
                Thread.currentThread().getThreadGroup().getParent().interrupt(); // 메인 스레드 인터럽트
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        interruptingThread.start();

        // 메인 스레드 일시 중지 및 인터럽트 대기
        try {
            Thread.sleep(5000); // 메인 스레드 5초 대기
        } catch (InterruptedException e) {
            System.out.println("메인 스레드 인터럽트됨");
        }

        System.out.println("메인 스레드 종료");
    }
}

출력 예시:

메인 스레드 실행 중...
데몬 스레드 실행 중...
데몬 스레드 실행 중...
데몬 스레드 실행 중...
메인 스레드 인터럽트 시도...
메인 스레드 인터럽트됨
메인 스레드 종료
  • 3초 후에 "메인 스레드 인터럽트 시도..." 메시지가 출력되며, 메인 스레드가 인터럽트됩니다.

  • 이후 메인 스레드는 InterruptedException을 발생시키고 "메인 스레드 인터럽트됨" 메시지를 출력한 후 종료됩니다.

코드 설명

  • 데몬 스레드 설정: daemonThread.setDaemon(true);로 스레드를 데몬 스레드로 설정했습니다. 이는 스레드가 시작되기 전에만 설정할 수 있습니다.

  • 데몬 스레드의 실행: 데몬 스레드는 백그라운드에서 무한 루프로 실행되도록 설정되어 있지만, 메인 스레드가 종료되면 자동으로 종료됩니다.

  • 데몬 스레드는 1초 간격으로 "데몬 스레드 실행 중..."을 출력합니다. 이 스레드는 메인 스레드가 종료되면 자동으로 종료됩니다.

  • interruptingThread:

    • 새로운 스레드 interruptingThread는 3초 동안 대기(Thread.sleep(3000))한 후, 메인 스레드를 인터럽트합니다.

    • Thread.currentThread().getThreadGroup().getParent().interrupt()를 호출하여 현재 실행 중인 메인 스레드를 인터럽트합니다.

  • 메인 스레드:

    • 메인 스레드는 5초 동안 Thread.sleep(5000)으로 일시 중지됩니다.

    • 그러나 interruptingThread에 의해 3초 후에 메인 스레드는 인터럽트되어 InterruptedException이 발생하고, "메인 스레드 인터럽트됨" 메시지가 출력됩니다.

동기화**(Synchronization)**

동기화(Synchronization)란?

  • 동기화(Synchronization)는 멀티스레드 환경에서 여러 스레드가 동시에 공유 자원(예: 변수, 객체)에 접근할 때 발생할 수 있는 문제를 방지하기 위해 사용되는 기법입니다.

  • 자바에서 동기화는 주로 임계 영역(Critical Section)을 보호하여 스레드 간의 데이터 불일치를 예방하고, 데이터의 일관성을 유지하는 데 사용됩니다.

동기화가 없다면 여러 스레드가 동시에 공유 자원에 접근할 수 있으며, 이로 인해 데이터 경쟁(Race Condition)이나 데이터 손상(Corruption)이 발생할 수 있습니다. 동기화를 통해 공유 자원에 대한 스레드의 접근을 제어하면 이러한 문제를 방지할 수 있습니다.

1. 자바에서의 동기화 구현 방법

자바에서는 동기화를 구현하기 위해 synchronized 키워드와 동기화 블록(Synchronized Block)을 사용합니다.

1.1 synchronized 메소드

method 앞에 synchronized 키워드를 사용하여 해당 메소드가 한 번에 하나의 스레드에 의해서만 실행될 수 있도록 합니다.

edu.ch13.synchronization.synchronizedMethod 패키지

public class Counter {
    private int count = 0;

    // synchronized 메소드로 설정하여 동기화
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
  • synchronized 키워드: increment 메소드가 동기화되어 있으므로, 여러 스레드가 동시에 이 메소드를 호출해도 한 번에 하나의 스레드만 실행할 수 있습니다.

  • 임계 영역: count++ 연산은 임계 영역으로 보호되며, 다른 스레드가 increment 메소드를 실행하려면 현재 실행 중인 스레드가 메소드를 완료하고 나서야 가능합니다.

1.2 synchronized 블록

method 전체가 아니라 특정 코드 블록만 동기화하고자 할 때, synchronized 블록을 사용합니다. 이 방식은 주로 특정 객체에 대한 동기화가 필요한 경우에 사용됩니다. edu.ch13.synchronization.synchronizedBlock 패키지

public class Counter {
    private int count = 0;

    public void increment() {
        // 동기화 블록을 사용하여 특정 객체를 동기화
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}
  • synchronized (this): 현재 인스턴스(this)에 대한 동기화를 수행합니다. 이 블록 내의 코드는 한 번에 하나의 스레드만 실행할 수 있습니다.

  • 동기화 대상: synchronized 블록에 사용된 객체(여기서는 this)를 모니터(lock)라고 부르며, 이 객체를 통해 스레드 간의 접근을 제어합니다.

1.3 synchronized 블록을 사용하여 특정 객체 동기화

edu.ch13.synchronization.synchronizedObject 패키지

다른 객체에 대한 동기화도 가능합니다. 예를 들어, 공유 객체 lock을 사용하여 동기화할 수 있습니다.

public class Counter {
    private int count = 0;
    private final Object lock = new Object();  // 동기화에 사용할 객체

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

2. 동기화의 중요성: 동기화 문제 예시

동기화가 이루어지지 않은 경우 여러 스레드가 동시에 자원에 접근하여 데이터 경쟁이 발생할 수 있습니다.

동기화 없이 동작하는 코드 예시

edu.ch13.synchronization.importance 패키지

public class UnsafeCounter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        UnsafeCounter counter = new UnsafeCounter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        // 예상 결과는 2000이지만, 출력 결과가 항상 2000이 아닐 수 있습니다.
        System.out.println("최종 카운트: " + counter.getCount());
    }
}

출력 예시

최종 카운트: 1503
  • 예상 출력은 2000이지만, 스레드 간 경쟁으로 인해 실제로는 예측할 수 없는 값이 출력될 수 있습니다.

  • count++ 연산은 세 단계로 나누어지는데, 이 과정 중 다른 스레드가 개입하여 올바른 값이 저장되지 않을 수 있습니다.

3. 동기화된 코드로 문제 해결

위 코드를 synchronized 키워드를 사용하여 동기화하면 예상한 결과를 얻을 수 있습니다. edu.ch13.synchronization.importance 패키지

public class SafeCounter {
    private int count = 0;

    // synchronized 메소드로 동기화
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        SafeCounter counter = new SafeCounter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        // 동기화된 결과로 항상 2000이 출력됩니다.
        System.out.println("최종 카운트: " + counter.getCount());
    }
}

출력 예시

최종 카운트: 2000

4. 동기화의 영향

  • 성능 저하: 동기화를 사용하면 여러 스레드가 임계 영역에 접근하는 속도가 제한되므로 프로그램의 성능이 저하될 수 있습니다. 따라서 필요한 부분에만 동기화를 적용해야 합니다.

  • 교착 상태(Deadlock): 동기화된 코드가 잘못 설계되면 교착 상태가 발생할 수 있습니다. 여러 스레드가 서로의 락을 기다리면서 영원히 대기하는 상태가 될 수 있으므로, 동기화 블록의 사용을 신중하게 설계해야 합니다.

5. 정리

  • 동기화는 여러 스레드가 동시에 공유 자원에 접근하는 것을 제어하여 데이터의 일관성을 유지하는 데 사용됩니다.

  • 자바에서는 synchronized 키워드를 사용하여 메소드 전체 또는 특정 블록을 동기화할 수 있습니다.

  • 동기화는 데이터 경쟁과 데이터 손상을 방지하지만, 성능에 영향을 줄 수 있으므로 필요한 곳에만 사용해야 합니다.

  • 동기화 블록을 잘못 설계하면 교착 상태가 발생할 수 있으므로, 주의하여 작성해야 합니다.

스레드 풀

스레드 풀(Thread Pool)이란?

  • 스레드 풀(Thread Pool)은 미리 일정한 수의 스레드를 생성해두고, 작업 큐에서 요청된 작업들을 처리하는 스레드 관리 기법입니다. 스레드 풀을 사용하면 스레드를 효율적으로 관리하고, 스레드 생성 및 종료로 인한 비용을 줄일 수 있습니다. 이는 특히 동시성을 필요로 하는 대규모 작업에 매우 유용합니다.

쓰레드 풀
물품 대여 업체

쓰레드

업체 소유 물품

(대수 지정, 사용 후 반납 & 재사용)

Runnable

이용자

(모두가 물품 이용중이면 대기)

1. 스레드 풀의 필요성

  • 성능 향상: 매번 작업이 필요할 때마다 새로운 스레드를 생성하고 종료하는 것은 상당한 비용이 듭니다. 스레드 풀은 미리 생성된 스레드를 재사용하여 이 비용을 줄입니다.

  • 자원 관리: 시스템에서 생성할 수 있는 스레드의 수는 제한적입니다. 스레드 풀이 없으면 무한정 많은 스레드를 생성할 수 있어 시스템의 메모리나 CPU 자원을 낭비하고, 심지어 시스템 다운을 초래할 수 있습니다. 스레드 풀은 제한된 수의 스레드를 생성하여 자원을 효율적으로 관리합니다.

  • 과부하 방지: 스레드 풀이 있으면 지정된 수의 스레드만 활성화되기 때문에, 시스템에 무리한 부하를 주지 않고 안정적으로 동작할 수 있습니다.

2. 자바에서 스레드 풀 사용: Executor 프레임워크

자바에서는 Executor 프레임워크를 통해 스레드 풀을 간편하게 사용할 수 있습니다. Executor 인터페이스는 스레드를 관리하고 작업을 실행하는 방법을 정의하며, 다양한 종류의 스레드 풀을 제공하는 정적 메소드를 포함하는 Executors 클래스와 함께 사용됩니다.

2.1. Executors 클래스로 스레드 풀 생성하기

  • newFixedThreadPool(int nThreads): 고정된 크기의 스레드 풀을 생성합니다. 스레드 풀 내의 스레드 수는 항상 고정되어 있으며, 모든 스레드가 사용 중일 때 새로운 작업이 도착하면 대기 큐에 추가됩니다.

  • newCachedThreadPool(): 필요한 경우 스레드를 생성하고, 사용하지 않는 스레드를 재사용합니다. 시스템에 부하를 줄 수 있어 주의해서 사용해야 합니다.

  • newSingleThreadExecutor(): 단일 스레드로 작업을 처리하는 스레드 풀을 생성합니다.

  • newScheduledThreadPool(int nThreads): 주기적 또는 지연 작업을 처리하는 스레드 풀을 생성합니다.

3. 스레드 풀 사용 예제

다음 예제에서는 Executors.newFixedThreadPool() 메소드를 사용하여 고정된 크기의 스레드 풀을 생성하고, 여러 작업을 스레드 풀에 제출하여 처리하는 방법을 보여줍니다.

edu.ch13.threadPool 패키지

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadPoolExample {
    public static void main(String[] args) {
        // 3개의 스레드로 구성된 고정 크기 스레드 풀 생성
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 5개의 작업을 스레드 풀에 제출
        for (int i = 1; i <= 8; i++) {
            int taskNumber = i;
            executor.submit(() -> {
                System.out.println("작업 " + taskNumber 
	                + "을 실행하는 스레드: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000); // 작업 시뮬레이션
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("작업 " + taskNumber + " 완료");
            });
        }

        // 더 이상 작업을 받지 않으며, 현재 진행 중인 작업들이 끝나면 스레드 풀 종료
        executor.shutdown();
    }
}

출력 예시

작업 1을 실행하는 스레드: pool-1-thread-1
작업 5을 실행하는 스레드: pool-1-thread-5
작업 3을 실행하는 스레드: pool-1-thread-3
작업 4을 실행하는 스레드: pool-1-thread-4
작업 2을 실행하는 스레드: pool-1-thread-2
작업 4 완료
작업 1 완료
작업 2 완료
작업 5 완료
작업 3 완료
작업 6을 실행하는 스레드: pool-1-thread-5
작업 8을 실행하는 스레드: pool-1-thread-2
작업 7을 실행하는 스레드: pool-1-thread-1
작업 7 완료
작업 8 완료
작업 6 완료

코드 설명

  1. 스레드 풀 생성:

    • Executors.newFixedThreadPool(5)를 사용하여 5개의 스레드로 구성된 고정 크기 스레드 풀을 생성합니다.

    • 이 스레드 풀은 동시에 최대 7개의 작업을 처리할 수 있습니다.

  2. 작업 제출:

    • executor.submit() 메소드를 사용하여 8개의 작업을 스레드 풀에 제출합니다.

    • 스레드 풀의 크기가 5이므로 동시에 5개의 작업이 실행되며, 나머지 작업은 이전 작업이 완료될 때까지 대기합니다.

  3. 스레드 풀 종료:

    • executor.shutdown()을 호출하여 스레드 풀을 종료합니다. 이 메소드는 더 이상 새로운 작업을 받지 않고, 현재 진행 중인 작업이 완료되면 스레드 풀을 종료합니다.

4. 스레드 풀의 주요 메소드

  • submit(Runnable task): 스레드 풀에 작업을 제출합니다. Runnable 또는 Callable 객체를 인수로 받아 스레드 풀에서 실행합니다.

  • shutdown(): 스레드 풀이 더 이상 새로운 작업을 받지 않고, 현재 진행 중인 작업이 모두 완료되면 스레드 풀을 종료합니다.

  • shutdownNow(): 스레드 풀을 즉시 종료하고, 진행 중인 작업도 중단시킵니다.

  • awaitTermination(long timeout, TimeUnit unit): 스레드 풀이 종료될 때까지 대기하며, 대기 시간 동안 종료되지 않으면 false를 반환합니다.

5. 주의사항

  • shutdown()과 shutdownNow():

    • shutdown(): 더 이상 새로운 작업을 받지 않지만, 이미 제출된 작업이 모두 완료될 때까지 스레드 풀을 유지합니다.

    • shutdownNow(): 즉시 스레드 풀을 종료하고, 진행 중인 작업도 중단시킵니다.

  • 무한정한 스레드 생성 방지: newCachedThreadPool()은 필요한 경우 새로운 스레드를 계속 생성할 수 있으므로, 작업 수가 매우 많은 경우 조심해서 사용해야 합니다.

  • 자원 해제: 스레드 풀은 작업이 완료된 후에도 계속 유지되기 때문에 반드시 shutdown()을 호출하여 스레드 풀을 종료하고 자원을 해제해야 합니다.

6. 정리

  • 스레드 풀은 미리 생성된 스레드 집합을 사용하여 여러 작업을 동시에 처리하는 방식으로, 스레드 생성 및 소멸에 따른 비용을 최소화하고 시스템 자원을 효율적으로 관리합니다.

  • 자바에서는 Executors 클래스를 통해 다양한 스레드 풀(newFixedThreadPool, newCachedThreadPool, newSingleThreadExecutor, newScheduledThreadPool)을 쉽게 생성할 수 있습니다.

  • 스레드 풀을 사용하여 멀티스레딩 작업을 수행할 때는 반드시 shutdown()을 호출하여 스레드 풀을 적절히 종료해야 합니다.

Callable의 개념

자바에서 멀티스레딩을 사용하면 동시에 여러 작업을 수행할 수 있습니다. 이때, 작업을 실행하는 가장 기본적인 방법은 Runnable을 사용하여 스레드를 만드는 것입니다. 그러나 Runnable은 반환값이 없고 예외 처리를 할 수 없는 단순한 작업만 수행할 수 있습니다.

어떤 작업을 멀티스레딩으로 수행하면서 그 결과를 받아와야 하는 경우가 생깁니다. 예를 들어, 데이터베이스 쿼리, 파일 읽기, 계산 작업 등이 있으며 이러한 작업이 끝난 후 그 결과를 다시 사용할 수 있어야 합니다. 이러한 경우에 Callable을 사용할 수 있습니다.

1. Callable과 Runnable의 차이점

Runnable과 Callable의 가장 큰 차이점은:

  1. 반환값: Runnable은 반환값이 없지만, Callable은 제네릭을 사용하여 반환값을 지정할 수 있습니다.

  2. 예외 처리: Callable은 call() 메소드에서 체크 예외를 던질 수 있어 작업 중에 발생하는 예외를 호출하는 쪽에서 처리할 수 있습니다.

2. Callable 사용 예시

edu.ch13.callable 패키지

import java.util.concurrent.Callable;

public class CallableExample {
    public static void main(String[] args) {
        // Callable 작업 정의
        Callable<Integer> task = () -> {
            int sum = 0;
            for (int i = 1; i <= 10; i++) {
                sum += i;
            }
            return sum; // 결과 반환
        };

        try {
            // Callable의 call() 메소드 직접 호출
            Integer result = task.call();  // 비동기 실행 없이 호출하는 예시
            System.out.println("합계: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

코드 설명

  1. Callable 인터페이스:

    • Callable<Integer>는 Integer 타입의 결과를 반환하는 작업을 정의합니다.

    • task는 람다 표현식으로 정의된 Callable 객체로, call() 메소드에서 1부터 10까지의 합을 계산하여 반환합니다.

  2. call() 메소드 직접 호출:

    • 이 예시에서는 Callable을 사용하지만 Future나 스레드 풀을 사용하지 않고, 단순히 task.call() 메소드를 직접 호출하여 작업을 실행하고 그 결과를 출력합니다.

    • 예외가 발생할 수 있으므로 try-catch 블록으로 감싸서 예외를 처리합니다.

3. Callable의 이점

  • 반환값: Callable을 통해 작업의 결과를 반환할 수 있습니다.

  • 예외 처리: Callable은 call() 메소드에서 체크 예외를 던질 수 있으므로, 작업 중 발생하는 예외를 유연하게 처리할 수 있습니다.

  • 복잡한 작업: 계산, 파일 I/O, 데이터베이스 액세스 등 복잡한 작업을 수행하는 데 적합합니다.

Future 개념

  • Future는 자바의 java.util.concurrent 패키지에서 제공되는 인터페이스로, 비동기 작업의 결과를 나타내는 객체입니다. Future는 작업이 완료될 때까지 기다리거나, 작업을 취소하거나, 작업의 상태를 확인하고 결과를 가져오는 방법을 제공합니다. 이 인터페이스를 사용하면 현재 진행 중인 작업의 완료 여부를 비동기적으로 추적하고 그 결과를 나중에 사용할 수 있습니다.

1. Future의 주요 메소드

Future는 비동기 작업의 상태와 결과를 관리하기 위한 여러 메소드를 제공합니다.

  • get(): 작업이 완료될 때까지 현재 스레드를 블록(대기)하며, 완료되면 결과를 반환합니다.

  • get(long timeout, TimeUnit unit): 작업이 완료될 때까지 주어진 시간만큼 기다립니다. 시간이 초과되면 TimeoutException을 발생시킵니다.

  • cancel(boolean mayInterruptIfRunning): 작업을 취소합니다. 작업이 이미 완료되었거나, 취소할 수 없는 상태라면 false를 반환하고, 그렇지 않으면 true를 반환합니다.

  • isDone(): 작업이 완료되었는지 여부를 반환합니다. 완료 상태는 작업이 정상적으로 종료되거나, 취소되거나, 예외가 발생했을 때를 포함합니다.

  • isCancelled(): 작업이 취소되었는지 여부를 반환합니다.

2. Future의 사용 예시

다음은 Callable과 ExecutorService를 사용하여 비동기 작업을 실행하고, Future를 통해 그 결과를 가져오는 예시입니다.

edu.ch13.future 패키지

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 FutureExample {
    public static void main(String[] args) {
        // 스레드 풀 생성
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // Callable 작업 정의
        Callable<String> task = () -> {
            Thread.sleep(2000); // 2초 동안 작업 수행
            return "작업 완료!";
        };

        // Callable 작업을 실행하고 Future 객체로 결과 받기
        Future<String> futureResult = executor.submit(task);

        // 작업 상태 확인
        System.out.println("작업이 완료되었는가? " + futureResult.isDone());

        try {
            // 작업 결과 가져오기
            String result = futureResult.get(); // 작업이 완료될 때까지 대기
            System.out.println("작업 결과: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 작업이 완료되었는지 확인
        System.out.println("작업이 완료되었는가? " + futureResult.isDone());

        // 스레드 풀 종료
        executor.shutdown();
    }
}

출력 예시

작업이 완료되었는가? false
작업 결과: 작업 완료!
작업이 완료되었는가? true

코드 설명

  1. 스레드 풀 생성:

    • Executors.newFixedThreadPool(2)를 사용하여 두 개의 스레드로 구성된 스레드 풀을 생성합니다.

  2. Callable 작업 정의:

    • 람다 표현식으로 정의된 Callable<String> 작업을 만듭니다.

    • call() 메소드에서 2초 동안 작업을 수행한 후 "작업 완료!"라는 문자열을 반환합니다.

  3. 작업 제출 및 Future 객체 반환:

    • executor.submit(task)를 사용하여 작업을 비동기적으로 실행하고, 작업의 결과를 Future<String> 객체로 받습니다.

  4. isDone() 사용:

    • futureResult.isDone()을 호출하여 작업이 완료되었는지 확인합니다.

    • 작업이 아직 진행 중이므로 처음에는 false가 출력됩니다.

  5. get() 메소드로 결과 가져오기:

    • futureResult.get() 메소드를 호출하면 현재 스레드가 작업이 완료될 때까지 대기하며, 작업이 완료되면 결과를 반환합니다.

    • 이 메소드는 예외(InterruptedException, ExecutionException)를 던질 수 있으므로 try-catch 블록으로 감싸서 예외를 처리합니다.

  6. 스레드 풀 종료:

    • 모든 작업이 완료되었으므로 executor.shutdown()을 호출하여 스레드 풀을 종료합니다.

CompletableFuture란?

  • CompletableFuture는 자바 8에서 도입된 비동기 프로그래밍을 지원하는 클래스입니다. CompletableFuture는 비동기 작업의 결과를 처리하고, 여러 비동기 작업을 결합하거나 후속 작업을 정의하는 데 사용할 수 있는 강력한 기능을 제공합니다. Future의 확장된 버전으로, Future가 가지고 있던 몇 가지 단점을 보완하고 추가적인 비동기 프로그래밍 기능을 제공합니다.

1. CompletableFuture의 주요 특징

  • 비동기 작업 실행: CompletableFuture를 사용하면 비동기적으로 작업을 실행하고 결과를 처리할 수 있습니다.

  • 후속 작업 정의: 작업이 완료되었을 때 자동으로 수행될 후속 작업(콜백)을 정의할 수 있습니다.

  • 조합 및 합성: 여러 비동기 작업을 결합하거나 합성할 수 있습니다.

  • 에러 처리: 작업 중 발생하는 예외를 처리할 수 있는 방법을 제공합니다.

  • 논블로킹(Non-blocking) 프로그래밍: 결과를 기다리지 않고 작업을 수행하며, 작업이 완료되면 콜백을 통해 결과를 처리합니다.

2. CompletableFuture의 생성 및 비동기 실행

2.1. CompletableFuture 생성 및 사용 예시

CompletableFuture를 사용하여 간단한 비동기 작업을 실행하고 결과를 처리하는 예시입니다.

edu.ch13.future 패키지

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        // 비동기 작업 생성
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);  // 2초 동안 작업 수행
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "작업 완료!";
        });

        // 결과 처리 (비동기적으로 수행)
        future.thenAccept(result -> {
            System.out.println("결과: " + result);
        });

        System.out.println("메인 스레드 계속 실행...");

        // 메인 스레드 종료를 방지하기 위해 잠시 대기
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

출력 예시

메인 스레드 계속 실행...
결과: 작업 완료!

코드 설명

  1. 비동기 작업 생성:

    • CompletableFuture.supplyAsync() 메소드를 사용하여 비동기 작업을 시작합니다.

    • 이 작업은 2초 동안 대기한 후 "작업 완료!"라는 결과를 반환합니다.

  2. 결과 처리:

    • future.thenAccept(result -> {...})를 사용하여 작업이 완료된 후 결과를 비동기적으로 처리하는 콜백을 정의합니다. 이 콜백은 작업이 완료되면 호출됩니다.

  3. 논블로킹 실행:

    • CompletableFuture는 비동기적으로 실행되므로, 작업이 완료될 때까지 main 메소드의 다른 코드는 계속 실행됩니다.

3. 다양한 비동기 작업 실행 방법

CompletableFuture는 비동기 작업을 실행하기 위한 여러 메소드를 제공합니다.

  • runAsync(Runnable runnable): 결과를 반환하지 않는 비동기 작업을 실행합니다.

  • supplyAsync(Supplier<U> supplier): 결과를 반환하는 비동기 작업을 실행합니다.

  • 두 메소드 모두 Executor를 인수로 받아 스레드 풀을 지정할 수 있습니다

edu.ch13.etcCompletableFuture 패키지 - ReturnResultOrNot 클래스

import java.util.concurrent.CompletableFuture;

public class ReturnResultOrNot {
    public static void main(String[] args) {
        // 결과를 반환하지 않는 비동기 작업 실행
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println("비동기 작업 실행 중...");
        });

        // 결과를 반환하는 비동기 작업 실행
        CompletableFuture<Integer> future2 
                = CompletableFuture.supplyAsync(() -> {
            return 42;
        });

        // future2 결과 출력
        future2.thenAccept(result -> {
            System.out.println("결과: " + result);
        });
    }
}

4. 후속 작업 및 콜백 처리

4.1. 작업 완료 후 콜백 처리

  • thenApply(Function): 결과를 받아 새로운 결과를 반환하는 콜백을 정의합니다.

  • thenAccept(Consumer): 결과를 받아 처리하지만 반환값이 없는 콜백을 정의합니다.

  • thenRun(Runnable): 결과를 사용하지 않고 단순히 실행할 콜백을 정의합니다.

edu.ch13.etcCompletableFuture 패키지

import java.util.concurrent.CompletableFuture;

public class CallBackProcessing {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            return "Hello, World!";
        }).thenApply(result -> {
            return result.toUpperCase();
        }).thenAccept(result -> {
            System.out.println("결과: " + result);
        });
    }
}

출력 예시

결과: HELLO, WORLD!

5. 여러 작업의 조합

5.1. 두 작업 조합

  • thenCombine(): 두 개의 CompletableFuture 결과를 조합하여 새로운 결과를 생성합니다.

edu.ch13.etcCompletableFuture 패키지

import java.util.concurrent.CompletableFuture;

public class CompletableFutureApply {
    public static void main(String[] args) {
        CompletableFuture<Integer> future1 
                = CompletableFuture.supplyAsync(() -> 2);
        CompletableFuture<Integer> future2 
                = CompletableFuture.supplyAsync(() -> 3);

        CompletableFuture<Integer> combinedFuture 
                = future1.thenCombine(future2, (a, b) -> a + b);

        combinedFuture.thenAccept(result -> {
            System.out.println("결합된 결과: " + result);
        });
    }
}

출력 예시

결합된 결과: 5

5.2. 모든 작업 완료 후 처리

edu.ch13.etcCompletableFuture 패키지 - ReturnResultOrNot 클래스

  • allOf(): 여러 CompletableFuture를 배열로 받아 모든 작업이 완료될 때까지 기다립니다.

import java.util.concurrent.CompletableFuture;

public class ReturnResultOrNot {
    public static void main(String[] args) {

        // 결과를 반환하지 않는 비동기 작업 실행
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println("비동기 작업 실행 중...");
        });

        // 결과를 반환하는 비동기 작업 실행
        CompletableFuture<Integer> future2 
                = CompletableFuture.supplyAsync(() -> {
            return 42;
        });

        // future2 결과 출력
        future2.thenAccept(result -> {
            System.out.println("결과: " + result);
        });

        // 모든 작업 완료 후 처리
        CompletableFuture<Void> allFutures 
			        = CompletableFuture.allOf(future1, future2);

        allFutures.thenRun(() -> {
            System.out.println("모든 작업이 완료되었습니다.");
        });
    }
}

6. 예외 처리

CompletableFuture에서는 비동기 작업 중 발생하는 예외를 처리하기 위한 메소드도 제공합니다.

  • exceptionally(Function): 예외가 발생했을 때 대체 결과를 반환하거나 처리합니다.

edu.ch13.etcCompletableFuture 패키지

public class Exceptionally {
    public static void main(String[] args) {
        CompletableFuture<Integer> future
                = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("에러 발생!");
            }
            return 42;
        }).exceptionally(ex -> {
            System.out.println("예외 처리: " + ex.getMessage());
            return 0;
        });
    }
}

출력 예시

예외 처리: 에러 발생!

7. CompletableFuture의 장점

  • 비동기 작업 간의 조합: 여러 비동기 작업을 순차적으로 또는 병렬로 실행하고, 그 결과를 조합할 수 있습니다.

  • 콜백 처리: 작업이 완료되면 자동으로 콜백이 실행되어 결과를 처리할 수 있습니다.

  • 에러 처리: 작업 중 발생하는 예외를 유연하게 처리할 수 있습니다.

  • 논블로킹: 작업이 완료될 때까지 대기하지 않고, 다른 작업을 계속할 수 있습니다.

8. 정리

  • CompletableFuture는 자바에서 비동기 작업을 수행하고 그 결과를 처리하는 데 매우 유용한 클래스입니다.

  • 다양한 메소드를 통해 비동기 작업의 실행, 조합, 후속 작업 처리, 예외 처리 등을 할 수 있습니다.

  • CompletableFuture는 Future의 한계를 보완하여 더 강력한 비동기 프로그래밍 기능을 제공합니다.

Previous오류 보충자료Next멀티태스킹 보충자료

Last updated 5 months ago