Phaser — 유연한 재사용 배리어
Phaser — 유연한 재사용 배리어
Phaser는 재사용 가능한 동기화 배리어로, 기능적으로 CyclicBarrier와 CountDownLatch와 비슷하지만 더 유연한 사용을 지원해요. 등록 파티 수가 시간에 따라 달라질 수 있다는 점이 핵심 차이예요.
본문
개념 이해하기
Phaser는 CyclicBarrier와 CountDownLatch와 비슷하지만 더 유연한, 재사용 가능한 동기화 배리어예요.
public class Phaser
extends Object
등록 (Registration)
다른 배리어와 달리 phaser에 동기화하기 위해 등록되는 파티 수는 시간에 따라 달라질 수 있어요. 태스크는 언제든 등록(register(), bulkRegister(int), 또는 초기 파티 수를 설정하는 생성자)할 수 있고, 도착 시 선택적으로 등록 해제(arriveAndDeregister())할 수 있어요. 등록·해제는 내부 카운트에만 영향을 미치며, 태스크는 자신이 등록됐는지 조회할 수 없어요 (서브클래스화로 추가 가능).
동기화
CyclicBarrier처럼 Phaser는 반복해서 대기할 수 있어요. arriveAndAwaitAdvance()은 CyclicBarrier.await과 유사한 효과예요. phaser의 각 세대(generation)는 연관된 위상(phase) 번호를 가지며, 0에서 시작해 모든 파티가 도착하면 진행(advance)되고 Integer.MAX_VALUE에 도달하면 0으로 돌아가요.
- Arrival(도착):
arrive()와arriveAndDeregister()는 도착을 기록해요. 이 메서드는 블로킹하지 않고 연관된 도착 위상 번호를 반환해요. 주어진 위상의 마지막 파티가 도착하면 선택적 액션이 수행되고 위상이 진행돼요. 이 액션은 위상 진행을 촉발한 파티가 수행하며, 종료를 제어하는onAdvance(int,int)를 오버라이드해 배치돼요. - Waiting(대기):
awaitAdvance(int)는 도착 위상 번호를 나타내는 인자를 요구하고, phaser가 다른 위상으로 진행될 때(또는 이미 다른 위상일 때) 반환해요. 대기 중인 스레드가 인터럽트돼도 계속 기다려요. 인터럽트 가능·타임아웃 변형도 있지만, 그 예외가 phaser의 상태를 바꾸진 않아요. Phaser는ForkJoinPool에서 실행되는 태스크에도 쓰일 수 있어요.
종료 (Termination)
isTerminated()로 확인할 수 있는 종료 상태에 들어갈 수 있어요. 종료 시 모든 동기화 메서드는 진행을 기다리지 않고 즉시 반환하며(음수 반환값으로 표시), 종료 후 등록 시도는 효과가 없어요. onAdvance 호출이 true를 반환하면 종료가 촉발돼요. 기본 구현은 등록 해제로 등록 파티 수가 0이 되면 true를 반환해요. forceTermination()은 대기 스레드를 급작스레 해제하고 종료시키는 데도 쓸 수 있어요.
계층화 (Tiering)
Phaser는 경합을 줄이기 위해 계층 구조(트리)로 구성될 수 있어요. 파티 수가 많아 동기화 경합 비용이 큰 경우, 하위 phaser 그룹이 공통 부모를 공유하게 만들 수 있어요. 트리에서 자식 phaser의 부모 등록·해제는 자동으로 관리돼요.
모니터링
동기화 메서드는 등록 파티만 호출할 수 있지만, phaser의 현재 상태는 모든 호출자가 모니터링할 수 있어요. getRegisteredParties(), getArrivedParties(), getPhase(), getUnarrivedParties()가 있어요.
메모리 일관성 효과: 어떤 형태의 arrive 메서드 이전의 동작은 대응하는 위상 진행과 onAdvance 액션(있다면)보다 happen-before이고, 이는 다시 위상 진행에 뒤따르는 동작보다 happen-before예요.
활용 예 — CountDownLatch 대체 (일회성)
void runTasks(List<Runnable> tasks) {
Phaser startingGate = new Phaser(1); // "1" to register self
for (Runnable task : tasks) {
startingGate.register();
new Thread(() -> {
startingGate.arriveAndAwaitAdvance();
task.run();
}).start();
}
startingGate.arriveAndDeregister(); // deregister self to allow threads to proceed
}
생성자
public Phaser() — 등록 파티 없음, 부모 없음, 초기 위상 0. 사용하려면 먼저 등록해야 해요.
public Phaser(int parties) — 주어진 등록·미도착 파티 수, 부모 없음, 초기 위상 0.
IllegalArgumentException
public Phaser(Phaser parent) — Phaser(parent, 0)와 동등.
public Phaser(Phaser parent, int parties) — 주어진 부모와 파티 수. 부모가 non-null이고 파티 수가 0보다 크면 자식 phaser가 부모에 등록돼요.
IllegalArgumentException
등록·도착 메서드
public int register() — 새 미도착 파티를 추가해요. 종료된 phaser면 등록이 효과 없고 음수 반환.
IllegalStateException(최대 파티 초과)
public int bulkRegister(int parties) — 주어진 수의 미도착 파티를 추가해요.
IllegalStateException,IllegalArgumentException(parties < 0)
public int arrive() — 다른 사람이 도착하길 기다리지 않고 이 phaser에 도착해요. 도착 위상 번호 또는 종료 시 음수 반환.
public int arriveAndDeregister() — 다른 사람을 기다리지 않고 도착하며 등록을 해제해요. 해제는 향후 위상 진행에 필요한 파티 수를 줄여요. 부모가 있고 0 파티가 되면 부모에서도 해제돼요.
public int arriveAndAwaitAdvance() — 도착하고 다른 사람을 기다려요. awaitAdvance(arrive())와 동등.
대기 메서드
public int awaitAdvance(int phase) — 주어진 위상 값에서 phaser의 위상이 진행될 때까지 기다려요. 현재 위상이 주어진 값과 다르거나 종료됐으면 즉시 반환.
phase— 보통 이전arrive/arriveAndDeregister반환값
public int awaitAdvanceInterruptibly(int phase) throws InterruptedException — 인터럽트되면 InterruptedException을 던지는 대기 변형.
public int awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException — 타임아웃 대기 변형.
종료·상태 메서드
public void forceTermination() — 이 phaser를 종료 상태로 강제해요. 계층화 세트면 세트의 모든 phaser가 종료돼요. 예외 후 복구 조정에 유용.
public final int getPhase() — 현재 위상 번호. Integer.MAX_VALUE 후 0으로 재시작. 종료 시 음수.
public int getRegisteredParties() — 등록된 파티 수.
public int getArrivedParties() — 현재 위상에 도착한 등록 파티 수.
public int getUnarrivedParties() — 아직 도착하지 않은 등록 파티 수.
public Phaser getParent() — 부모 또는 null.
public Phaser getRoot() — 루트 조상 (부모 없으면 자기 자신).
public boolean isTerminated() — 종료됐으면 true.
**protected boolean onAdvance(int phase, int registeredParties)``** — 임박한 위상 진행 시 액션 수행·종료 제어를 위한 오버라이드 메서드. true를 반환하면 종료 상태로 설정돼요. 계층화 세트에선 루트 phaser에서만 호출돼요. 기본 구현은 arriveAndDeregister로 등록 파티가 0이 되면 true. 연속 등록 시 계속 진행하게 하려면 항상 false`를 반환하도록 오버라이드하세요.
public String toString() — "phase = ...", "parties = ...", "arrived = ..."를 담은 상태 문자열.