구성 단위
자바 기본 라이브러리를 보면 병렬 프로그램 작성 시 필요한 여러 가지 동기화 도구가 마련되어있다.
동기화된 컬렉션 클래스
동기화된 컬렉션 클래스의 문제점
클라이언트 코드에서 두 개 이상의 연산을 묶어서 사용하는 경우
개별 연산에 대한 스레드 안전은 지켜주지만, 그 연산을 조합한 복합 연산은 적절한 lock을 클라이언트 코드가 호출해야한다 스레드 안전 켈렉션의 상태는 정상적인데 클라이언트 코드는 깨질 염려가 있다
스레드 안전 클래스가 제공하는 클라이언트 측 락을 사용해서 락을 걸어야한다. (e.g. 컬렉션 클래스 자체)
Iterator와 ConcurrentModificationException
즉시 멈춤(fail-fast) 반복문을 실행하는 도중 컬렉션 클래스 내부의 값을 변경하는 상황 포착시ConcurrentModificationException 예외 발생 및 멈추는 방법
숨겨진 Iterator
병렬 컬렉션
동기화된 컬렉션 클래스는 내부 변수에 접근하는 통로를 일련화해서 스레드 안전성을 확보 한 것이라서 동시 사용성은 상당히 손해본다.
병렬 컬렉션 👉🏻 여러 스레드에서 동시에 사용할 수 있도록 설계했다.
ConcurrentMap 인터페이스에는 putIfAbsent, replace, conditionalRemove 연산 등 자주 쓰이는 복합 연산을 단일 연산으로 내부적으로 제공하고 있다. 동기화 컬렉션을 병렬 컬렉션으로 교체만 해도 전체적인 성능을 상당히 끌어 올릴 수 있다.
ConcurrentHashMap
동기화 컬렉션과 다른 동기화 기법을 채택하면서 병렬성과 확장성이 나아졌다.
Iterator는 즉시 멈춤 대신 미약한 일관성 적략을 취한다.미약한 일관성 전략 > 반복문 중 컬렉션 내용 변경해도 Iterator 만든 시점 상황대로 반복 계속 가능, 변경 내용 반영해 동작할 수도 있다(보장되진 않음).
Map 기반의 또 다른 단일 연산
CopyOnWriteArrayList
스레드 안정성 확보 방법 > 불벽 객체는 외부에 공개해도, 별다른 동기화 과정 없어도 스레드 안전, 컬렉션 내용 변경 마다 복사본 새로 생성해서 불변 객체 처럼 처리
List에 들어 있는 값을 Iterator로 접근할 때 List 전체에 락을 걸거나 복제할 필요 ❌ 변경할 때마다 복사라는 개념은 불변 객체를 외부에 공개하면 스레드 안전하다는 개념 도입했기 때문에 가능
블로킹 큐와 프로듀서-컨슈머 패턴
BlockingQueue 핵심 메소드
프로듀서-컨슈머(producer-consumer) 패턴
구현체
직렬 스레드 한정
객체의 소유권을 producer → consumer 로 넘기는 과정에서 직렬 스레드 한정(serial thread confinement) 기법 사용
객체 풀(object pool)은 직렬 스레드 한정 기법 잘 활용하는 예 객체의 소유권을 빌려주고 (안전하게 공개), 풀에 반납
Deque, 작업 가로채기(work stealing)
BlockingDeque 인터페이스 존재 (기능은 우리가 아는 그 Deque + Blocking)
작업 가로채기 패턴
다른 consumer 작업을 가져올 때도 맨 뒤에서 작업을 가져오니 원래 소유자와 경쟁이 일어나지 않는다. 자신의 덱이 비었다고 쉬는 스레드가 없도록 관리한다.
블로킹 메소드, 인터럽터블 메소드
블로킹 연산 > 멈춘 상태에서 특정한 신호를 받아야 계속해서 실행할 수 있는 연산 InterruptedException을 발생시키는 메소드는 블로킹 메소드라는 의미
동기화 클래스
동기화 클래스(synchronizer) > 상태 정보를 활용, 스레드 간 작업 흐름 조절을 위한 클래스
동기화 클래스 구조적 특징
래치 (latch)
래치(latch) > terminal 상태에 이를 때까지 스레드가 멈추도록 해주는 동기화 클래스 스레드의 관문으로 생각할 수 있다. 래치가 terminal 상태에 다다르면 관문이 열리고 모든 스레드가 통과한다.
FutureTask
반환 값을 가지는 Callback 인터페이스를 구현 해야함 총 세가지 상태를 가짐 시작 전 대기, 시작됨, 종료됨 실제로 연산을 실행한 스레드에서 만든 객체를 안전한 공개 방법을 통해서 실행시킨 스레드에게 넘겨준다.
다음 상황에 유용
사용 방법 시점
세마포어 (semaphore)
카운팅 세마포어는 특정 자원, 연산을 동시에 사용하거나 호출할 수 있는 스레드 수 제한시 사용 [풀이나 컬렉션 크기에 제한을 두고자 할 때 유용 내부에 가상의 퍼밋(permit)을 만들어 상태 관리
외부 스레드
이진 세마포어는 비재진입 락 역할 하는 뮤텍스로 활용가능
배리어 (barrier)
배리어(barrier) > 다른 스레드를 기다리 위한 동기화 클래스
특정 이벤트가 발생할 때까지 여러 스레드를 대기 상태로 잡아둘 수 있다. 모든 스레드가 배리어 위치에 이르러야 관문이 열리고 계속 실행할 수 있다
실제 작업 병렬 처리하고, 다음 단계로 넘어가기 위해선 이전 단계의 모든 계산이 끝나야 할 때, 이전 단계 내용을 취합할 때 유용하다.
Last updated