# 15장 개선된 자바 동시성

김진욱·2022년 12월 2일
0

java

목록 보기
13/13

자바 7에서는 분할, 정복 알고리즘의 포크/조인 구현을 지원하는 java.util.concurrent.RecursiveTask가 추가되었고 자바 8에서는 스트림과 새로 추가된 람다 지원에 기반한 병렬 프로세싱이 추가 되었다.

자바는 Future를 조합하는 기능을 추가하면서 동시성을 강화했고, 자바 9에서는 분산 비동기 프로그래밍을 명시적으로 지원한다.

이들 API는 매쉬업 애플케이션, 즉 다양항 웹 서비스를 이용하고 이들 정보를 실시간으로 조합해 사용자에게 제공하거나 추가 웹 서비스를 통해 제공하는 종류의 애플리케이션을 개발하는데 필수적인 기초 모델과 툴킷을 제공한다. 이 과정을 리액티브 프로그래밍이라고 부르며 자바 9에서는 발행-구독 프로토콜(java.util.concurrent.Flow인터페이스 추가)로 이를 지원한다. CompletableFuturejava.util.concurrent.Flow의 궁극적인 목표는 가능한한 동시에 실행할 수 있는 독립적인 태스크를 가능하게 만들면서 멀티코어 또는 여러 기기를 통해 제공되는 병렬성을 쉽게 이용하는 것이다.

쓰레드 풀 그리고 쓰레드 풀이 더 좋은 이유

  • 자바 ExecutorService는 태스크를 제출하고 나중에 결과를 수집할 수 있는 인터페이스를 제공한다. 프로그램은 newFixedThreadPool 같은 팩토리 메서드 중 하나를 이용해 쓰레드 풀을 만들어 사용할 수 있다. 프로그래머는 태스크(Runnable이나 Callable) 를 제공하면 쓰레드가 이를 실행한다.

쓰레드 풀 그리고 쓰레드 풀이 나쁜 이유

  • A 쓰레드를 가진 쓰레드 풀은 오직 A만큼의 쓰레드를 동시에 실행할 수 있다. 초과로 제출된 태스크는 큐에 저장되며 이전에 태스크 중 하나가 종료되기 전까지는 쓰레드에 할당되지 않는다. 처음 제출한 태스크는 기존 실행 중인 태스크가 나중의 태스크 제출을 기다리는 상황(Future의 일반적인 패턴)이라면 데드락에 걸릴 수도 있다. 핵심은 블록(자거나 이벤트를 기다리는)할 수 있는 태스크는 스레드 풀에 제출하지 말아야 한다는 것이지만 항상 이를 지킬 수 있는 것은 아니다.
  • 프로그램을 종료하기 전에 모든 스레드 풀을 종료하는 습관을 갖는 것이 중요하다.(풀의 워커 쓰레드가 만들어진 다음 다른 태스크 제출을 기다리면서 종료되지 않은 상태일 수 있으므로)
  • 쓰레드 실행은 메서드를 호출한 다음의 코드와 동시에 실행되므로 데이터 경쟁 문제를 일으키지 않도록 주의해야 한다.

  • 기존 실행 중이던 쓰레드가 종료되지 않은 상황에서 자바의 main() 메서드가 반환하면 어떻게 될까?

    1. 애플리케이션을 종료하지 못하고 모든 쓰레드가 실행을 끝낼 때까지 기다린다.
    2. 애플리케이션 종료를 방해하는 쓰레드를 강제종료 시키고 애플리케이션을 종료한다.

두가지 방법 전부 안전하지 못하다.

첫 번째 방법에서는 종료를 못한 쓰레드에 의해 애플리케이션이 충돌될 수 있다. 또 다른 문제로 디스크에 쓰기 I/O 작업을 시도하는 일련의 작업을 중단했을 때 이로 인해 외부 데이터의 일관성이 파괴될 수 있다.

이 문제를 피하려면 만든 모든 쓰레드를 추적하고 애플리케이션을 종료하기 전에 쓰레드 풀을 포함한 모든 쓰레드를 종료하는 것이 좋다.

비동기 API에서 예외는 어떻게 처리하는가?

Future나 리액티브 형식의 비동기 API에서 호출된 메서드의 실제 바디는 별도의 스레드에서 호출되며 이때 발생하는 어떤 에러는 이미 호출자의 실행 범위와는 관계가 없는 상황이 된다.

Future를 구현한 CompletableFuture에서는 런타임 get() 메서드에 예외를 처리할 수 있는 기능을 제공하며 예외에서 회복할 수 있도록 exceptionally() 같은 메서드도 제공한다.

0개의 댓글

관련 채용 정보