gugbab2's GitBook
  • Language
    • C++
      • 강의
        • C++ 언매니지드 프로그래밍
          • C++ 프로그래밍
          • 출력(Output)
          • 입력(Input)
          • bool 타입, Reference
          • 상수(const)
          • 문자열(string)
          • 파일 입출력
          • 개체지향 프로그래밍1
          • 개체지향 프로그래밍2
          • 개체지향 프로그래밍3
          • 캐스팅(형변환, casting)
          • 인라인 함수
          • static 키워드
          • 예외(Exception)
          • STL(Standard Template Library) 컨테이너(Container) - Vector
          • STL 컨테이너 - Map
          • STL 컨테이너 - Queue, Stack, Set, List
          • 템플릿(Template) 프로그래밍
          • 새로운 키워드(C++11 ~) 1
          • 새로운 키워드(C++11 ~) 2
          • 새로운 자료형
          • 새로운 STL 컨테이너
          • 스마트(smart) 포인터
          • 이동생성자 및 이동대입연산자
          • constexpr
          • Lamda Expression
      • 책
        • The C++ Programming Lanuaage
          • 2부 : 기본 기능
            • 6. 타입과 선언
            • 7. 포인터, 배열, 참조
            • 8. 구조체(struct), 공용체(union), 열거형(enum)
            • 10. 표현식
            • 11. 선택 연산
            • 12. 함수
            • 13. 예외 처리
            • 15. 소스 파일과 프로그램
          • 3부 : 추상화 메커니즘
            • 16. 클래스
            • 17. 생성, 소멸, 복사와 이동
            • 18. 연산자 오버로딩
            • 19. 특수 연산자
            • 20. 파생클래스
        • 씹어먹는 C++
          • 2. C++ 참조자(reference) 의 도입
          • 5.1 연산자 오버로딩(비교, 대입 연산자)
          • 5-2. 연산자 오버로딩(이항, 입출력, 타입변환, 증감 연산자)
          • 6-2. 가상(virtual) 함수와 다형성
          • 6-3. 가상 함수에 대한 지식들
          • 9-1. 코드를 찍어내는 틀 - C++ 템플릿(template)
          • 9-2. 가변 길이 템플릿(Variadic template)
          • 9-3. 템플릿 메타 프로그래밍 (Template Meta Programming)
          • 9-4. 템플릿 메타 프로그래밍2
          • 16.1 유니폼 초기화(Uniform Initialization)
          • 토막글 2. 람다(lambda)
    • Java
      • 강의
        • 김영한의 실전 자바 - 기본편
          • 절차 지향 vs 객체 지향
            • 절차 지향 프로그래밍
            • 객체 지향 프로그래밍
          • 변수
            • 클래스 변수 / 인스턴스 변수, 멤버 변수 / 지역 변수
            • 기본형 vs 참조형
          • 패키지
            • 패키지
            • CLI 환경에서 .java 파일 컴파일 && 실행
          • 접근 제어자
            • 접근 제어자 - 기본
            • 캡슐화
          • static
            • 자바 메모리 구조
            • static 기본
            • 스택 영역, 힙 영역
              • 스택 영역, 힙 영역 - 기본
              • 메소드가 실행될 때 어떤일이 일어나는가?
          • 상속
            • 상속 기본
          • 다형성(Pilymorphism)
            • 다형성 기본
            • 다형성의 활용
              • 다형성의 활용 - 기본
              • 다형성의 활용 - 추상클래스
              • 다형성의 활용 - 인터페이스
            • 다형성과 설계
              • 좋은 객체 지향 프로그래밍
        • 김영한의 실전 자바 - 중급1편
          • 1. Object 클래스
          • 2. 불변 객체
          • 3. String 클래스
          • 4. 래퍼, Class 클래스
          • 5. 열거형 - ENUM
          • 6. 날짜와 시간
          • 7. 중첩 클래스, 내부 클래스1
          • 8. 중첩 클래스, 내부 클래스2
          • 9. 예외 처리1 - 이론
          • 10. 예외 처리 - 실습
        • 김영한의 실전 자바 - 중급2편
          • 1. 제네릭 - Generic1
          • 2. 제네릭 - Generic2
          • 3. 컬렉션 프레임워크 - ArrayList
          • 4. 컬렉션 프레임워크 - LinkedList
          • 5. 컬렉션 프레임워크 - List
          • 6. 컬렉션 프레임워크 - 해시(Hash)
          • 7. 컬렉션 프레임워크 - HashSet
          • 8. 컬렉션 프레임워크 - Set
            • 레드 블랙 트리
          • 9. 컬렉션 프레임워크 - Map, Stack, Queue
            • 왜(?) Set 은 내부에서 Map 을 사용할까?
          • 10. 컬렉션 프레임워크 - 순회, 정렬, 전체 정리
        • 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
          • 프로세스와 스레드 소개
          • 스레드 생성과 실행
          • 스레드 제어와 생명 주기1
          • 스레드 제어와 생명 주기2
          • 메모리 가시성
          • 동기화 - synchronized
            • synchronized 키워드 이해도 체크
          • 고급 동기화 - concurrent.Lock
          • 생산자 소비자 문제1
          • 생산자 소비자 문제2
          • CAS - 동기화와 원자적 연산
          • 동시성 컬렉션
          • 스레드 풀과 Executor 프레임워크1
          • 스레드 풀과 Executor 프레임워크2
        • 김영한의 실전 자바 - 고급 2편, I/O, 네트워크, 리플렉션
          • 문자 인코딩
          • I/O 기본1
          • I/O 기본2
          • I/O 활용
          • File, Files
          • 네트워크 - 프로그램1
          • 네트워크 - 프로그램2
          • 채팅 프로그램
          • HTTP 서버 만들기
          • 리플렉션
          • 애노테이션
          • HTTP 서버 활용
        • 김영한의 실전 자바 - 고급3편, 람다, 스트림, 함형 프로그래밍
          • 람다가 필요한 이유
          • 람다
          • 함수형 인터페이스
          • 람다 활용
          • 람다 vs 익명 클래스
          • 메서드 참조
          • 스트림API1 - 기본
          • 스트림 API2 - 기능
          • 스트림 API3 - 컬렉터
          • Optional
          • 디폴트 메서드
          • 병렬 스트림
          • 함수형 프로그래밍
        • 기초 탄탄! 독하게 시작하는 Java - Part2: OOP 와 JVM
          • 2. 클래스 - 첫 번째
          • 3. 클래스 - 두번째
          • 4. 상속과 관계
          • 6. JVM(Java Virtual machine) 기본 이론
          • 7. JVM 과 GC 그리고 객체
          • 8. 불변 객체와 String 클래스
      • 책
        • 자바의 신
          • 변수
            • 클래스 변수(static) 사용 주의 케이스
            • Java volatile 과 Atomic 변수(+CAS)
          • 연산자
            • 비트 연산자 활용 예제
          • 배열
          • 참조 자료형
          • 상속
          • Object 클래스
          • interface, abstract class, enum
          • 예외
          • String 클래스
            • String 구조
            • String 문자열을 byte 로 변환하기
            • String 클래스에서 자주 사용되는 메서드
            • String 클래스로 살펴보는 불변(Immutable)객체
            • StringBuilder, StringBuffer
          • Nested 클래스
          • 어노테이션
            • 어노테이션 기본
            • 어노테이션의 사용
          • JVM 이해하기
            • 왜 JVM 을 사용해?
            • JVM, JRE, JDK
            • JVM 구조 이해하기
            • 클래스 로더 시스템
            • JIT(Just-In-Time) 컴파일러
            • GC(Garbage Collector)
              • GC Part.1
              • GC Part.2
              • GC 튜닝
          • java.lang
            • Wrapper 클래스
            • System 클래스
          • Generic
            • 제네릭 기본
            • 와일드카드
            • 와일드카드 GET / SET 경계
            • 와일드카드 extends / super 사용시기
            • 혼동할 수 있는 와일드카드 표현
          • Collection
            • 자료구조
              • 이진 탐색 트리 vs 레드 블랙 트리
            • Collection
            • List
              • ArrayList
              • Vector
              • Stack
              • LinkedList
            • Set, Queue
              • HashSet
              • LinkedHashSet
              • TreeSet
              • Priority Queue
              • ArrayDeque
            • Map
              • HashMap
              • Hashtable
              • LinkedHashMap
              • TreeMap
          • Thread
            • Thread 기본
            • Thread 와 관련이 많은, Synchronized
            • Thread 를 통제하는 메서드
            • ThreadGroup
          • I/O
            • InputStream, OutputStream
            • Reader, Writer
          • Serializable, NIO
            • Serializable
            • NIO (New IO)
          • 네트워크 프로그래밍
            • 네트워크 기본 & TCP 통신
            • UDP 통신
          • 람다
            • 함수형 인터페이스
            • 람다란?
        • 벨둥(Bealdung)
          • Java Concurrency
            • Java Concurrency Basics
              • Overview of the java.util.concurrent
              • Guide to the Synchronized Keyword in Java
              • Guide to the Volatile Keyword in Java
              • Guide to the java.util.concurrent.Future
              • ThreadLocal in Java
      • 그 외
        • 시스템 콜과 자바에서의 시스템 콜 사용례
        • 자바 NIO 의 동작원리 및 IO 모델
        • 함수형 인터페이스(FunctionInterface) - 자바8
  • Spring
    • 강의
      • 스프링 핵심 원리 - 기본편
        • 큰 흐름 잡기
        • 스프링 핵심 원리 이해1 - 예제 만들기
        • 스프링 핵심 원리 이해2 - 객체 지향 원리 적용
        • 스프링 컨테이너와 스프링 빈
        • 싱글톤 컨테이너
        • 컴포넌트 스캔
        • 의존관계 자동 주입
        • 빈 생명주기 콜백
        • 빈 스코프
      • 토비의 스프링6 - 이해와 원리
        • 3. 오브젝트와 의존관계1
        • 3. 오브젝트와 의존관계2
        • 4. 테스트
        • 5. 템플릿
        • 6.예외
        • 7. 서비스 추상화
    • 책
      • JSP 2.3 웹 프로그래밍
        • Servlet
        • JSP
        • 쿠키 / 세션
        • MVC 패턴
        • 실무 때 고민할 만한 부분
      • 스프링 입문을 위한 자바 객체지향의 원리와 이해
        • 자바와 절차적/구조적 프로그래밍
        • 객체지향의 4대 특성
        • 객체지향 설계의 5원칙
        • 스프링이 사랑한 디자인 패턴
        • IoC / DI
        • AOP(Aspect Oriented Programming), 관점 지향 프로그래밍
      • 토비의 스프링 3.1
        • Spring vs Spring Boot
        • 1. 오브젝트와 의존관계
          • 1.4 제어의 역전(IoC)
          • 1.5 스프링의 IoC
          • 1.6 싱글톤 레지스트리와 오브젝트 스코프
    • 그 외
      • 스프링 부트(SpringBoot) 탄생 배경
  • CS
    • DATA STRUCTURES
      • 선택 정렬(Selection Sort)
      • 버블 정렬(Bubble Sort)
      • 삽입 정렬(Insertion Sort)
    • OS
      • 강의
      • 책
        • 혼자 공부하는 컴퓨터구조 + 운영체제
          • 1. 컴퓨터 구조 시작하기
          • 2. 데이터
          • 3. 명령어
          • 4. CPU 의 작동원리
          • 5. CPU 성능 향상 기법
          • 6. 메모리와 캐시메모리
          • 7. 보조기억장치
          • 8. 입출력장치
          • 9. 운영체제 시작하기
          • 10. 프로세스와 스레드
    • NETWORK
      • 그 외
        • REST API
          • REST API
          • URI & MIME type
          • Collection Pattern
          • Collection Pattern 적용
          • Spring Web MVC 구현
        • SSL 인증 동작
        • DTO & JSON & CROS
          • DTO
          • 직렬화(Serialization)
          • Jackson ObjectMapper
          • CROS
        • Connection Timeout / Read Timeout
      • 강의
        • 외워서 끝내는 네트워크 핵심이론 - 기초
          • Internet 기반 네트워크 입문
            • Host 는 이렇게 외우자
            • 스위치가 하는 일과 비용
          • L2 수준에서 외울 것들
            • NIC, L2 Frame, LAN 카드 그리고 MAC 주소
            • L2 스위치에 대해서
            • LAN 과 WAN 의 경계 그리고 Broadcast
          • L3 수준에서 외울 것들
            • IPv4 주소의 기본 구조
            • L3 IP Packet 으로 외워라
            • 패킷의 생성과 전달 및 계층별 데이터 단위
            • 이해하면 인생이 바뀌는 TCP/IP 송, 수신 구조
            • IP 헤더 형식
            • 서브넷 마스크와 CIDR
            • Broadcast IP 주소와 Localhost
            • TTL 과 단편화
            • 인터넷 설정 자동화를 위한 DHCP
            • ARP 과 Ping(RTT : Round Trip Time)
          • L4 수준 대표주자 TCP 와 UDP
            • TCP 와 UDP 개요
            • TCP 연결 및 상태 변화
            • TCP 연결 종료 및 상태 변화
            • TCP, UDP 헤더 형식과 게임서버 특징
            • TCP 가 연결이라는 착각
            • TCP 연결과 게임버그
          • 웹을 이루는 핵심기술
            • DNS
            • URL, URI
        • 외워서 끝내는 네트워크 핵심 이론 - 응용
          • 네트워크 장치의 구조
            • 세 가지 네트워크 장치 구조
            • Inline 구조
            • Out of path 구조와 DPI 그리고 망중립
            • Proxy(클라이언트 입장) - 우회
            • Proxy(클라이언트 입장) - 보호와 감시
            • Reverse Proxy(서버 입장)
          • 인터넷 공유기의 작동 원리
            • 공유기 개요
            • Symmetric NAT
            • Full Cone 방식
            • Restricted Cone, Port Restricted Cone
            • 포트 포워딩
            • UPnP 와 NAT
          • 부하분산 시스템 작동 원리
            • L4 부하분산 무정지 시스템
            • 대규모 부하분산을 위한 GSLB
          • VPN과 네트워크 보안 솔루션
            • PN 과 VPN
            • IPSec VPN 과 터널링 개념
            • VPN 과 재택근무
        • 외워서 끝내는 SSL 과 최소한의 암호기술
          • 기초이론
            • Checksum (검사합)
            • Hash
          • 암호기술에 대한 이해
            • 대칭키
            • 비대칭키
          • PKI 시스템과 인터넷
            • 인터넷을 위한 비대칭키 체계
            • 공개키 신뢰를 위한 검증체계
            • 웹서비스와 공인인증서
      • 책
        • 그림으로 배우는 네트워크 원리
          • 1. 네트워크 기본
          • 2. 네트워크를 만드는 것
          • 3. 네트워크의 공통 언어 TCP/IP
    • SECURITY
      • 그 외
        • Basic Auth
        • HMAC 기반 인증
    • 그 외
      • 동기/비동기 & 블로킹/논블록킹
  • DB
    • 그 외
      • 인덱스(Index)
      • 트랜잭션(TRANSACTION)
      • 실무에서 외래키를 사용하지 않는 이유
      • ORM vs SQL Mapper
      • 문자열 vs DATE
      • EXPLAIN 명령어
    • 강의
      • Real MySQL 시즌 1
        • Part.1
          • 1강. CHAR vs VARCHAR
          • 2강. VARCHAR vs TEXT
          • 3강. COUNT(*) & COUNT(DISTINCT) 튜닝
          • 4강. 페이징 쿼리 작성
          • 5강. Stored Function
      • 토크온 41차. JPA 프로그래밍 기본 다지기
        • 1. JPA 소개
        • 2. JPA 기초와 매핑
        • 3. 필드와 컬럼 매핑
        • 4. 연관관계 매핑
        • 5. 양방향 매핑
        • 6. JPA 내부구조
        • 7. JPA 객체지향쿼리
        • 8. Spring Data JPA 와 QueryDSL 이해
    • 책
  • Software Development Methodology
    • TDD
      • 강의
        • Spring Boot TDD - 입문부터 실전까지 정확하게
          • 세션2. TDD 소개
          • 세션5. API 설계
          • 세션6. TDD 주기 첫 번째 경험
          • 세션7. TDD 주기 반복
      • 그 외
        • 단위 테스트(Unit Test) 작성의 필요성
        • JUnit5
          • A Guide to JUnit 5
          • Guide to JUnit 5 Parameterized Tests
          • AssertJ Exception Assertions
          • Testing in Spring Boot
          • Junit 과 Mockito 기반의 Spring 단위 테스트 코드 작성법
        • Code Coverage
          • Code Coverage?
    • DDD
      • 책
        • 도메인 주도 설계(Domain-Driven Design)
          • 04 - 도메인의 격리
          • 05 - 소프트웨어에서 표현되는 모델
          • 06 - 도메인 객체의 생명주기
          • 07 - 언어의 사용(확장 예제) (1)
          • 07 - 언어의 사용(확장 예제) (2)
        • 도메인 주도 개발 시작하기
          • 1. 도메인 모델 시작하기
          • 2. 아키텍처 개요
          • 3. 애그리거트
          • 4. 리포지터리와 모델 구현
            • DAO vs Repository
      • 강의
        • DDD 세레나데(NEXTSTEP)
          • 1주차
            • 도메인 주도 설계 등장 배경
            • 레거시 코드
            • 유연한 설계 - ASSERTION
          • 2주차
            • 전략적 설계 - UBIQUITOUS LANGUAGE
            • 전략적 설계 - BOUNDED CONTEXT
          • 3주차
            • 전술적 설계 - VALUE OBJECT 와 ENTITY
            • 전술적 설계 - AGGREGATE 와 REPOSITORY
            • 전술적 설계 - SERVICE
    • REFACTORING
      • 일급 컬렉션(First Class Collection) 소개와 사용해야하는 이유
  • ARCHITECTURE
    • Event Driven Architecture
  • 멘토링
    • F-Lab
      • 10회차(2024.12.29)
Powered by GitBook
On this page
  • 1. Executor
  • 2. ExecutorService
  • 라이프사이클 관리를 위한 기능들
  • 비동기 작업을 위한 기능들
  • 3. ScheduledExcutorService
  • 4. Executors
  • 5. Future
  • 6. CountDownLatch (단일 이벤트 대기용으로 사용)
  • 7. CyclicBarrier (쓰레드 간 협력이 필요한 경우 사용)
  • 8. Semaphore (자원에 대한 접근을 제어 시 사용)
  • 9. ThreadFactory
  1. Language
  2. Java
  3. 책
  4. 벨둥(Bealdung)
  5. Java Concurrency
  6. Java Concurrency Basics

Overview of the java.util.concurrent

PreviousJava Concurrency BasicsNextGuide to the Synchronized Keyword in Java

Last updated 7 months ago

참고 링크

1. Executor

  • 동시에 여러 요청을 처리해야 하는 경우에 매번 새로운 쓰레드를 만드는 것은 비효율적이다.

  • 그래서 쓰레드를 미리 만들어두고 재사용하기 위한 쓰레드 풀(Thread Pool) 이 등장하게 되었는데,

  • Executor 인터페이스는 쓰레드 풀의 구현을 위한 인터페이스이다.

  • 이러한 Executor 인터페이스를 간단히 정리하면 다음과 같다.

    • 등록된 작업(Runnable) 을 실행하기 위한 인터페이스

    • 작업 등록과 작업 실행 중에서 작업 실행만을 책임짐

  • 쓰레드는 크게 작업의 등록과 실행으로 나누어진다. 그 중에서도 Executor 인터페이스는 인터페이스 분리 원칙(Interface Segregation Principle) 에 맞게 등록된 작업을 실행하는 책임만 갖는다. -> 그래서 전달 받은 작업(Runnable) 을 실행하는 메서드만 가지고 있다.

public interface Executor {
    void execute(Runnable command);
}
  • Executor 인터페이스는 개발자들이 해당 작업의 실행과 쓰레드의 사용 및 스케줄링 등으로부터 벗어날 수 있도록 도와준다.

  • 단순히 전달 받은 Runnable 작업을 사용하는 코드를 Executor 로 구현하면 다음과 같다.

public class RunExecutor implements Executor {
    @Override 
    public void execute(final Runnable command) {
        command.run();
    }
}

@Test
void executorRun() {
    final Runnable runnable = () 
            -> System.out.println("Thread : " + Thread.cuncurrentThread.getName());

    Executor executor = new RunExecutor(); 
    executor.execute(runnable);
}
  • 하지만 위와 같은 코드는 단순히 객체의 메서드를 호출하는 것이므로, 새로운 쓰레드가 아닌 메인 쓰레드에서 실행된다.

  • 만약 위의 코드를 새로운 쓰레드에서 실행시키려면 Executor 의 execute 메서드를 다음과 같이 수정하면 된다.

public class StartExecutor implements Executor {
    @Override 
    public void execute(final Runnable command) {
        new Thread(command).start(); 
    }
}

@Test
void executorRun() {
    final Runnable runnable = () 
            -> System.out.println("Thread : " + Thread.cuncurrentThread.getName());

    Executor executor = new StartExecutor(); 
    executor.execute(runnable);
}

2. ExecutorService

  • ExecutorService 는 작업(Runnable, Callable) 등록을 위한 인터페이스이다.

  • ExecutorService 는 Executor 를 상속 받아서 작업 등록(관리) 뿐 아니라 실행을 위한 책임도 갖는다.

  • 그래서 쓰레드 풀은 기본적으로 ExecutorService 인터페이스를 구현한다.

    • 대표적으로 ThreadPoolExecutor 가 ExecutorService 의 구현체인데,

    • ThreadPoolExecutor 내부에 있는 블로킹 큐에 작업들을 등록해둔다.

  • 위와 같이 크기가 2인 쓰레드 풀이 있다고 가정했을 때, 각각의 쓰레드는 작업을 할당 받아 처리한다.

  • 만약 사용 가능한 쓰레드가 없다면 작업은 블로킹 큐에 대기한다. 그러다 쓰레드가 작업이 끝나면 다음 작업을 할당받게 되는 것이다.

  • 이러한 ExecutorService 가 제공하는 퍼블릭 메서드들은 다음과 같이 분류 가능하다.

    • 라이프사이클 관리를 위한 기능들

    • 비동기 작업의 실행을 위한 기능들 (Thread)

라이프사이클 관리를 위한 기능들

제공 메서드

  • void shutdown()

    • 새로운 작업들을 더 이상 받아들이지 않음

    • 호출 전에 이미 실행중인 작업들은 그대로 실행이 끝나고 종료된다.

  • List<Runnable> shutdownNow()

    • shutdown 기능에 더해 이미 실행 중인 작업들을 인터럽트를 발생시킨다.

    • 추후 실행을 위해 대기중인 작업 목록을 반환한다.

  • boolean isShutdown()

    • Executor 의 shutdown 여부를 반환함

  • boolean isTerminated()

    • shuutdown 실행 후, 모든 작업의 종료 여부를 반환함

  • boolean awaitTerminated(long timeout, TimeUnit unit)

    • shutdown 실행 후, 지정한 시간 동안 모든 작업이 종료될 때 까지 대기한다.

    • 지정한 시간 내에 모든 작업이 종료되었는지 여부를 반환함

라이프사이클 관리 기능 사용 예제

  • ExecutorService 를 만들어 작업을 실행하면, shutdown 이 호출되기 전까지 계속해서 작업을 대기하게 된다.

  • 그러므로 작업이 완료 되었다면 반드시 shutdown 을 명시적으로 호출해주어야 한다.

@Test
void shutdown() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Runnable runnable = () -> System.out.println("Thread : " + Thread.currentThread().getName());
    executorService.execute(runnable); 
    
    // shutdown 호출 
    executorService.shutdown(); 
    
    // shutdown 호출 이후에는 새로운 작업들을 받을 수 없음, 에러 발생
    RejectedExecutionException result = assertThrows(RejectedExecutionException.class, () -> executorService.execute(runnable));
    assertThat(result).isInstanceOf(RejectedExecutionException.class);
}
  • 만약 작업 실행 후 shutdown 을 해주지 않으면 다음과 같이 프로세스가 끝나지 않고, 계속해서 다음 작업을 기다리게 된다.

  • 다음의 Main 메서드를 실행해보면 애플리케이션이 끝나지 않음을 확인할 수 있다.

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Runnable runnable = () -> System.out.println("Thread: " + Thread.currentThread().getName());
    executorService.execute(runnable);
}
  • shutdownNow 시에 중요한 것은, 만약 실행중인 작업들에서 인터럽트 여부에 따른 처리 코드가 없다면 계속 실행된다는 것이다. -> shutdownNow 의 경우 인터럽트를 발생시킨다.

  • 그러므로 필요하다면 다음과 같이 인터럽트 시 추가적인 조치를 취해 주어야 한다.

@Test
void shutdownNow() throws InterruptedException { 
    Runnable runnable = () -> {
        System.out.println("Start");
        while(true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println("Interrupt");
                break; 
            }
        }
        System.out.println("End");
    };
    
    ExecutorService executorService = Executors.newFixedThreadPool(10); 
    executorService.execute(runnable); 
    
    executorService.shutdownNow();
    Thread.sleep(1000L);
}

비동기 작업을 위한 기능들

  • ExecutorService 는 Runnable 과 Callable 을 작업으로 사용하기 위한 메서드를 제공한다.

  • 동시에 여러 작업들을 실행시키는 메서드도 제공하고 있는데, 비동기 작업의 진행을 추적할 수 있도록 Future 을 반환한다.

  • 반환 된 Future 들은 모두 실행된 것이므로 반환된 isDone 은 true 이다.

  • 하지만 작업들은 정상적으로 종료 되었을수도 있고, 예외에 의해 종료되었을 수도 있기 때문에, 항상 성공한 것은 아니다.

제공 메서드

  • submit

    • 실행할 작업들을 추가하고, 작업의 상태와 결과를 포함하는 Future 를 반환한다.

    • Future 의 get 을 호출하면 성공적으로 작업이 완료된 후 결과를 얻을 수 있다.

  • invokeAll

    • 모든 결과가 나올 때 까지 대기하는 블로킹 방식의 요청

    • 동시에 주어진 작업들을 모두 실행하고, 전부 끝나면 각가의 상태와 결과를 갖는 List(Future) 를 반환함

  • invokeAny

    • 가장 빨리 실행된 결과가 나올 때 까지 대기하는 블로킹 방식의 요청

    • 동시에 주어진 작업들을 모두 실행하고, 가장 빨리 완료된 하나의 결과를 Callable의 타입으로 반환함

비동기 작업 기능 사용 예제 (invokeAll)

  • ExecutorService 의 구현체로는 AbstractExecutorService 가 있는데, ExecutorService 의 메서드들 (submit, invokeAll, invokeAny) 에 대한 기본 구현들을 제공한다.

  • invokeAll 은 최대 쓰레드 풀의 크기만큼 작업을 동시에 실행시킨다.

  • 그러므로 쓰레드가 충분하다면 동시에 실행되는 작업 중 가장 오래 걸리는 시간 만큼 시간이 소요된다.

  • 하지만, 만약 쓰레드가 부족하다면 대기되는 작업들이 발생하므로 가장 오래 걸리는 작업의 시간에 더해 추가적으로 시간이 필요하다.

@Test
void invokeAll() throws InterruptedException, ExecutionException {
    ExecutorService executorService = Executors.newFixedThreadPool(10); 
    Instant start = Instant.now(); 
    
    Callable<String> hello = () -> {
        Thread.sleep(1000L);
        final String result = "Hello";
        System.out.println("result : " + result);
        return result;
    };
    
    Callable<String> world = () -> {
        Thread.sleep(4000L);
        final String result = "World ";
        System.out.println("result : " + result);
        return result;
    };

    Callable<String> gugbab2 = () -> {
        Thread.sleep(2000L);
        final String result = "gugbab2 ";
        System.out.println("result : " + result);
        return result;
    };
    
    // Arrays.asList로 변경
    List<Future<String>> futures = executorService.invokeAll(Arrays.asList(hello, world, gugbab2));  
    for (Future<String> f : futures) {
        System.out.println(f.get());
    }
    
    // 시간 계산에서 getSeconds()를 제거
    System.out.println("time : " + Duration.between(start, Instant.now()).toMillis() + " ms");
    
    executorService.shutdown(); 

}

비동기 작업 기능 사용 예제 (invokeAny)

  • invokeAny 는 가장 빨리 끝난 작업 결과만을 구하므로, 동시에 실행한 작업들 중에서 가장 짧게 걸리는 작업만큼 시간이 걸린다.

  • 또한, 가장 빠르게 처리된 작업 외의 나머지 작업들은 완료되지 않았으므로 취소처리 된다.

@Test
void invokeAny() throws InterruptedException, ExecutionException {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Instant start = Instant.now();

    Callable<String> hello = () -> {
        Thread.sleep(1000L);
        final String result = "Hello";
        System.out.println("result = " + result);
        return result;
    };

    Callable<String> mang = () -> {
        Thread.sleep(2000L);
        final String result = "Mang";
        System.out.println("result = " + result);
        return result;
    };

    Callable<String> kyu = () -> {
        Thread.sleep(3000L);
        final String result = "kyu";
        System.out.println("result = " + result);
        return result;
    };

    String result = executorService.invokeAny(Arrays.asList(hello, mang, kyu));
    System.out.println("result = " + result + " time = " + Duration.between(start, Instant.now()).getSeconds());

    executorService.shutdown();
}

3. ScheduledExcutorService

  • ScheduledExecutorService 는 ExecutorService 를 상속받는 인터페이스로써,

  • 특정 시간 이후 or 주기적으로 작업을 실행시키는 메서드가 추가되었다.

  • 그래서 특정 시간대에 작업을 실행하거나 주기적으로 작업을 실행하고 싶을 때 등에 사용할 수 있다.

제공 메서드

  • schedule

    • 단일 작업을 특정 시간 후, 한번만 실행한다.

  • scheduleAtFixedRate

    • 일정한 주기로 작업을 반복해서 실행한다.

    • 작업이 실행되고 특정 시간마다 작업을 진행시킴

  • schedultWithFixedDelay

    • 일정한 주기로 작업을 반복해서 실행한다.

    • 작업이 완료되고 특정 시간마다작업을 진행시킴

@Test
void schedule() throws InterruptedException {
    Runnable runnable = () -> System.out.println("Thread: " + Thread.currentThread().getName());
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    executorService.schedule(runnable, 1, TimeUnit.SECONDS);

    Thread.sleep(2000L);
    executorService.shutdown();
}

@Test
void scheduleAtFixedRate() throws InterruptedException {
    Runnable runnable = () -> {
        System.out.println("Start scheduleAtFixedRate:    " + LocalTime.now());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Finish scheduleAtFixedRate:    " + LocalTime.now());
    };
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    executorService.scheduleAtFixedRate(runnable, 0, 2, TimeUnit.SECONDS);

    Thread.sleep(10000L);
    executorService.shutdown();
}


@Test
void scheduleWithFixedDelay() throws InterruptedException {
    Runnable runnable = () -> {
        System.out.println("Start scheduleWithFixedDelay:    " + LocalTime.now());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Finish scheduleWithFixedDelay:    " + LocalTime.now());
    };

    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.scheduleWithFixedDelay(runnable, 0, 2, TimeUnit.SECONDS);

    Thread.sleep(10000L);
    executorService.shutdown();
}

4. Executors

  • 앞서 살펴본 Executor, ExecutorService, ScheduleExecutorService 는 쓰레드 풀을 위한 인터페이스이다.

  • 직접 쓰레드를 다루는 것은 번거로우므로, 이를 도와주는 팩토리 클래스인 Executors 가 등장하게 되었다.

  • Executors 는 고수준의 동시성 프로그래밍 모델로써 Executor, ExecutorService, ScheduleExecutorService 를 구현한 쓰레드 풀을 손쉽게 생성해준다.

  • Executors 를 통해 쓰레드의 개수 및 종류를 정할 수 있으며, 이를 통해 쓰레드의 생성과 실행 및 관리가 매우 용이해진다.

  • 하지만 쓰레드 풀 생성 시 주의를 해야한다.

    • 만약 newFixedThreadPool 을 사용해 2개의 쓰레드를 갖는 쓰레드 풀을 생성했는데, 3개의 작업이라면, 1개의 작업을 대기 후 실행해야 한다..

제공 메서드

  • newFixedThreadPool

    • 고정된 쓰레드 개수를 갖는 쓰레드 풀을 생성함

    • ExecutorService 인터페이스를 구현한 ThreadPoolExecutor 객체가 생성됨

  • newCachedThreadPool

    • 필요할 때 필요한 만큼의 쓰레드 풀을 생성함

    • 이미 생성된 쓰레드가 있다면 이를 재활용 할 수 있다.

  • newScheduleThreadPool

    • 일정 시간 뒤 혹은 주기적으로 실행되어야 하는 작업을 위한 쓰레드 풀을 생성함

    • ScheduledExecutorService 인터페이스를 구현한 ScheduledThreadPoolExecutor 객체가 생성됨

  • newSingleThreadExecutor, newSingleThreadScheduledExecutor

    • 1개의 쓰레드만을 갖는 쓰레드 풀을 생성함

    • 각각 newFixedThreadPool, newScheduledThreadPool 에 1개의 쓰레드만을 생성하도록 한 것이다.

5. Future

  • Future 는 비동기 작업의 결과를 나타내는 데 사용된다.

  • 비동기적으로 실행되는 작업의 상태나 결과를 나중에 확인할 수 있도록 하는 인터페이스이다.

  • 주로 ExecutorService 에서 비동기 작업을 제출할 때 반환된다. Future 를 사용하면 작업이 완료될 때까지 기다리거나, 작업의 취소, 상태 확인 등을 할 수 있다.

제공 메서드

  • T get()

    • 작업이 완료될 때까지 기다렸다가 결과를 반환한다.

    • 작업이 완료되면 그 결과를 반환하고, 작업이 완료되지 않았다면 해당 쓰레드가 작업을 완료할 때까지 기다린다.

  • T get(long timeout, TimeUnit unit)

    • 지정한 시간(timeout) 동안 작업이 완료되기를 기다린다.

    • 지정된 시간이 초과되면 TimoutException 을 던지고, 그렇지 않으면 작업 결과를 반환한다.

  • boolean cancel(boolean mayInterruptIfRunning)

    • mayInterruptIfRunning 이 true 이면 작업이 실행 중일 때도 인터럽트로 작업을 중단할 수 있고,

    • false 이면 작업이 시작되지 않았거나, 대기 중일 때만 취소된다.

  • boolean isCancelled()

    • 작업이 취소되었는지 여부를 반환한다.

  • boolean isDone()

    • 작업이 완료되었는지 여부를 반환한다.

ExecutorService executorService = Executors.newFixedThreadPool(10); 

// Future 인스턴스 생성 
Future<String> future = executorService.submit(() -> {
    // ...
    Thread.sleep(10000l);
    return "Hello world";
});

// Future 결과가 준비되었는지 확인하고, 실행이 완료되면 데이터를 가져올 수 있다. 
if (future.isDone() && !future.isCancelled()) {
    try {
        str = future.get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

// 또한, 주어진 작업에 대해서 타임아웃을 지정할 수 있다. 
// 작업이 지정한 시간보다 오래 걸리면 TimeoutException 이 발생한다. 
try {
    future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    e.printStackTrace();
}

6. CountDownLatch (단일 이벤트 대기용으로 사용)

  • CountDownLatch 는 특정 개수의 작업이 완료될 때까지 하나 이상의 쓰레드가 기다리도록 하는 동기화 도구이다.

  • 재사용이 불가하다.

  • 이 클래스는 멀티스테드 환경에서 작업을 조율하는데 매우 유용하다.

제공 메서드

  • await()

    • 이 메서드를 호출한 쓰레드는 CountDownLatch 의 Count 가 0이 될 때까지 대기한다.

    • Count가 0이 되면 대기중인 모든 쓰레드는 실행하게 된다.

  • countDown()

    • 이 메서드를 호출할 때마가 Count가 1씩 감소한다.

    • Count 가 0이 되면 대기중인 모든 쓰레드는 실행하게 된다.

  • getCount()

    • 현재 Count 값을 반환하는 메서드이다.

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        Thread worker1 = new Thread(() -> {
            try {
                System.out.println("Worker 1 is working...");
                Thread.sleep(1000);
                System.out.println("Worker 1 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        });

        Thread worker2 = new Thread(() -> {
            try {
                System.out.println("Worker 2 is working...");
                Thread.sleep(2000);
                System.out.println("Worker 2 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        });

        Thread worker3 = new Thread(() -> {
            try {
                System.out.println("Worker 3 is working...");
                Thread.sleep(3000);
                System.out.println("Worker 3 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        });

        worker1.start();
        worker2.start();
        worker3.start();

        // Count 가 3인 latch 에서 작업이 마무리 된 후 latch 를 실행한다. 
        latch.await();

        System.out.println("All workers finished, main thread proceeding");
    }
}

7. CyclicBarrier (쓰레드 간 협력이 필요한 경우 사용)

  • 여러 쓰레드가 특정 지점(장벽) 에 도착할 때까지 대기하며, 도착한 후 동작을 실행

  • 해당 객체를 여러번 사용이 가능하다.

제공 메서드

  • await()

    • 모든 스레드가 도착할 때까지 대기합니다.

  • reset()

    • 장벽을 초기 상태로 재설정합니다.

  • getNumberWaiting()

    • 현재 대기 중인 스레드 수를 반환합니다.

  • isBroken()

    • 장벽이 깨졌는지 여부를 확인합니다.

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int numberOfThreads = 3;
        CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
            System.out.println("All threads reached the barrier, let's proceed.");
        });

        Runnable task = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " is doing work");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " reached the barrier");
                barrier.await(); // 모든 스레드가 barrier에 도착할 때까지 대기
                System.out.println(Thread.currentThread().getName() + " is proceeding");
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        // 세 개의 스레드가 협력하여 동시에 실행
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

8. Semaphore (자원에 대한 접근을 제어 시 사용)

  • 자원에 대한 접근을 제어하는 도구로, 제한된 수의 쓰레드만이 자원에 접근할 수 있도록 허용

  • permit 이라는 개념을 통해서 자원의 접근을 제어

제공 메서드

  • acquire()

    • 자원을 요청하고, 자원이 허용되면 작업을 진행한다. 자원이 없으면 대기 ..

  • release()

    • 작업이 끝난 후 자원을 반환하여 다른 쓰레드가 자원에 접근할 수 있도록 한다.

  • availablePermits()

    • 자원에 접근 가능한 쓰레드 수를 반환한다.

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2); // 동시에 2개의 스레드만 자원에 접근 가능

        Runnable task = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " is waiting for a permit");
                semaphore.acquire(); // 자원 접근 요청
                System.out.println(Thread.currentThread().getName() + " acquired a permit");
                Thread.sleep(2000); // 자원 사용
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + " released a permit");
                semaphore.release(); // 자원 반환
            }
        };

        // 4개의 스레드가 있으나, 동시에 2개만 자원에 접근 가능
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}

9. ThreadFactory

  • 필요에 따라서 새로운 쓰레드를 만드는 쓰레드 풀의 역할을 한다.

  • ThreadFactory 에서 newThread 메서드를 사용하게 되면 런타임에 동적으로 새로운 쓰레드를 생성할 수 있다는 장점이 있다.

public class BaeldungThreadFactory implements ThreadFactory {
    private int threadId;
    private String name;

    public BaeldungThreadFactory(String name) {
        threadId = 1;
        this.name = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, name + "-Thread_" + threadId);
        LOG.info("created new thread with id : " + threadId +
            " and name : " + t.getName());
        threadId++;
        return t;
    }
}

// ...

BaeldungThreadFactory factory = new BaeldungThreadFactory( 
    "BaeldungThreadFactory");
for (int i = 0; i < 10; i++) { 
    Thread t = factory.newThread(new Task());
    t.start(); 
}

...

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html
https://mangkyu.tistory.com/259
https://www.baeldung.com/java-util-concurrent