park와 스레드 대기 상태
interrupt 플래그가 park 뒤 유지되는 동작과 시간 제한 대기, 모니터 진입 대기의 상태 차이를 재현합니다.
LockSupport 대기는 InterruptedException을 던지지 않습니다.
interrupt 상태가 설정되면 park가 반환하고 플래그는 그대로 남습니다.
이 차이는 sleep·wait·join의 예외·플래그 소거 동작과 대비됩니다.
시간 대기에는 parkNanos가 있지만 반환 원인은 여전히 조건 재검사로 판단해야 합니다.
interrupt 뒤 park 동작
다음 작업자는 스스로 interrupt한 뒤 park를 두 번 호출합니다.
플래그가 지워지지 않아 두 호출 모두 즉시 반환하고 실제로 wrong-second-wait=false, flag=true가 출력됩니다.
import java.util.concurrent.locks.LockSupport;
public final class ParkInterruptFlagAssumption {
public static void main(String[] args) throws InterruptedException {
Thread worker =
new Thread(
() -> {
Thread.currentThread().interrupt();
LockSupport.park();
boolean afterFirst = Thread.currentThread().isInterrupted();
LockSupport.park();
System.out.println(
"wrong-second-wait="
+ !afterFirst
+ ", flag="
+ Thread.currentThread().isInterrupted());
},
"flagged-parker");
worker.start();
worker.join();
}
}interrupt를 취소로 처리한다면 park 반환 뒤 플래그를 보고 반복문을 종료합니다.
요청을 소비하고 계속 대기하기로 명시했다면 Thread.interrupted()로 플래그를 지울 수 있지만 상위 취소 신호를 잃지 않는지 검토해야 합니다.
park·interrupt 상태 전이
허가증이 없는 park에 들어가면 스레드는 WAITING이 될 수 있습니다.
다른 스레드의 interrupt는 대기 중인 park를 반환하게 하며 인터럽트 플래그를 설정합니다.
park는 검사 예외를 강제하지 않으므로 호출자가 조회를 빼먹어도 컴파일됩니다.
sleep은 interrupt 시 InterruptedException을 던지고 catch 진입 시 플래그가 false입니다.
인터럽트로 돌아온 park는 예외를 던지거나 플래그를 지우지 않습니다.
공통 도우미가 둘을 섞어 사용하면 각 API의 소거 규칙을 문서화해야 합니다.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.LockSupport;
public final class ParkInterruptObservation {
public static void main(String[] args) throws InterruptedException {
CountDownLatch entered = new CountDownLatch(1);
Thread worker =
new Thread(
() -> {
entered.countDown();
LockSupport.park("cancel-observation");
System.out.println(
"returned flag=" + Thread.currentThread().isInterrupted());
},
"park-observer");
worker.start();
entered.await();
while (worker.getState() != Thread.State.WAITING) {
Thread.onSpinWait();
}
System.out.println("before=" + worker.getState());
worker.interrupt();
worker.join();
System.out.println("after=" + worker.getState());
}
}인터럽트 플래그와 상태 관측은 보장 범위가 다르다
자기 interrupt 후 두 park 반환에서도 플래그 유지, 별도 상태 폴링 경로의 순간 관측을 보여 줍니다. 각 프로그램의 조회·중단·종료를 순차 강조하되 관측 보장을 만들지 않습니다.
ParkInterruptFlagAssumption의 자기 인터럽트 결과와 ParkInterruptObservation의 세 출력 지점을 구분합니다.
PARK · 플래그와 상태
인터럽트 플래그와 상태 관측은 보장 범위가 다르다
park는 인터럽트 플래그를 지우지 않습니다. 플래그 계약과 before·after 상태 조회의 보장 범위를 구분합니다.
자기 interrupt 예제
- 읽을 수 있는 결과
- 두 park 뒤에도
flag=true입니다. - 그 결과의 범위
- 플래그를 지우지 않으므로
wrong-second-wait=false이며 대기 시간을 측정한 값은 아닙니다.
관측 예제의 before
- 읽을 수 있는 결과
- WAITING 폴링 뒤 상태를 새로 읽습니다.
- 그 결과의 범위
- 새 조회도 WAITING이라고 고정할 수 없습니다.
관측 예제의 작업자 로그
- 읽을 수 있는 결과
- park 뒤
isInterrupted()를 읽습니다. - 그 결과의 범위
- 임의 반환이 앞서면 false일 수 있고 before 로그와의 순서도 바뀔 수 있습니다.
관측 예제의 after
- 읽을 수 있는 결과
- 성공한 join 뒤 상태를 읽습니다.
- 그 결과의 범위
- 이 지점까지 완료했을 때는 TERMINATED입니다.
entered 래치는 park 호출보다 먼저 해제됩니다. main이 WAITING 순간을 놓치면, 작업자가 임의 반환 후 종료했어도 상태 폴링에서 계속 기다릴 수 있습니다.
이 예의 조건
- 자기 interrupt 두 park 실행과 entered/WAITING 폴링 실행은 독립 프로그램입니다.
- park는 플래그를 소거하지 않으며 상태 관측 반복에는 상한이 없습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 자기 interrupt 실험의 두 park는 flag true를 유지합니다. 별도의 entered-before park/상태 폴링/정상 join의 조회는 다른 보장 범위이며 false flag/관측 실패 가능성을 숨기지 않습니다. |
| 9 | 소거 안 함 |
| 구분 | 내용 |
|---|---|
| 설명 | 자기 interrupt 실험의 두 park는 flag true를 유지합니다. 별도의 entered-before park/상태 폴링/정상 join의 조회는 다른 보장 범위이며 false flag/관측 실패 가능성을 숨기지 않습니다. |
| 전체 읽기 정보 | 인터럽트 플래그와 상태 관측은 보장 범위가 다르다 인터럽트 플래그와 상태 관측은 보장 범위가 다르다 ParkInterruptFlagAssumption의 자기 인터럽트 결과와 ParkInterruptObservation의 세 출력 지점을 구분합니다. PARK · 플래그와 상태 인터럽트 플래그와 상태 관측은 보장 범위가 다르다 park는 인터럽트 플래그를 지우지 않습니다. 플래그 계약과 before·after 상태 조회의 보장 범위를 구분합니다. 자기 interrupt 예제 읽을 수 있는 결과 두 park 뒤에도 flag=true 입니다. 그 결과의 범위 플래그를 지우지 않으므로 wrong-second-wait=false 이며 대기 시간을 측정한 값은 아닙니다. 관측 예제의 before 읽을 수 있는 결과 WAITING 폴링 뒤 상태를 새로 읽습니다. 그 결과의 범위 새 조회도 WAITING이라고 고정할 수 없습니다. 관측 예제의 작업자 로그 읽을 수 있는 결과 park 뒤 isInterrupted() 를 읽습니다. 그 결과의 범위 임의 반환이 앞서면 false일 수 있고 before 로그와의 순서도 바뀔 수 있습니다. 관측 예제의 after 읽을 수 있는 결과 성공한 join 뒤 상태를 읽습니다. 그 결과의 범위 이 지점까지 완료했을 때는 TERMINATED입니다. entered 래치는 park 호출보다 먼저 해제됩니다. main이 WAITING 순간을 놓치면, 작업자가 임의 반환 후 종료했어도 상태 폴링에서 계속 기다릴 수 있습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 독립 시작 조건 자기 요청 프로그램은 플래그를 세우고 상태 관측 프로그램은 entered를 알립니다. |
|
|
| 대기와 조회 범위 각 독립 프로그램의 park와 관측을 같은 시점에서 비교합니다. |
|
|
| 후속 확인 각 프로그램이 확인할 수 있는 마지막 사실을 비교합니다. |
|
|
인터럽트 플래그와 상태 관측은 보장 범위가 다르다
자기 interrupt 후 두 park 반환에서도 플래그 유지, 별도 상태 폴링 경로의 순간 관측을 보여 줍니다. 각 프로그램의 조회·중단·종료를 순차 강조하되 관측 보장을 만들지 않습니다.
ParkInterruptFlagAssumption의 자기 인터럽트 결과와 ParkInterruptObservation의 세 출력 지점을 구분합니다.
PARK · 플래그와 상태
인터럽트 플래그와 상태 관측은 보장 범위가 다르다
park는 인터럽트 플래그를 지우지 않습니다. 플래그 계약과 before·after 상태 조회의 보장 범위를 구분합니다.
자기 interrupt 예제
- 읽을 수 있는 결과
- 두 park 뒤에도
flag=true입니다. - 그 결과의 범위
- 플래그를 지우지 않으므로
wrong-second-wait=false이며 대기 시간을 측정한 값은 아닙니다.
관측 예제의 before
- 읽을 수 있는 결과
- WAITING 폴링 뒤 상태를 새로 읽습니다.
- 그 결과의 범위
- 새 조회도 WAITING이라고 고정할 수 없습니다.
관측 예제의 작업자 로그
- 읽을 수 있는 결과
- park 뒤
isInterrupted()를 읽습니다. - 그 결과의 범위
- 임의 반환이 앞서면 false일 수 있고 before 로그와의 순서도 바뀔 수 있습니다.
관측 예제의 after
- 읽을 수 있는 결과
- 성공한 join 뒤 상태를 읽습니다.
- 그 결과의 범위
- 이 지점까지 완료했을 때는 TERMINATED입니다.
entered 래치는 park 호출보다 먼저 해제됩니다. main이 WAITING 순간을 놓치면, 작업자가 임의 반환 후 종료했어도 상태 폴링에서 계속 기다릴 수 있습니다.
이 예의 조건
- 자기 interrupt 두 park 실행과 entered/WAITING 폴링 실행은 독립 프로그램입니다.
- park는 플래그를 소거하지 않으며 상태 관측 반복에는 상한이 없습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 자기 interrupt 실험의 두 park는 flag true를 유지합니다. 별도의 entered-before park/상태 폴링/정상 join의 조회는 다른 보장 범위이며 false flag/관측 실패 가능성을 숨기지 않습니다. |
| 9 | 소거 안 함 |
| 구분 | 내용 |
|---|---|
| 설명 | 자기 interrupt 실험의 두 park는 flag true를 유지합니다. 별도의 entered-before park/상태 폴링/정상 join의 조회는 다른 보장 범위이며 false flag/관측 실패 가능성을 숨기지 않습니다. |
| 전체 읽기 정보 | 인터럽트 플래그와 상태 관측은 보장 범위가 다르다 인터럽트 플래그와 상태 관측은 보장 범위가 다르다 ParkInterruptFlagAssumption의 자기 인터럽트 결과와 ParkInterruptObservation의 세 출력 지점을 구분합니다. PARK · 플래그와 상태 인터럽트 플래그와 상태 관측은 보장 범위가 다르다 park는 인터럽트 플래그를 지우지 않습니다. 플래그 계약과 before·after 상태 조회의 보장 범위를 구분합니다. 자기 interrupt 예제 읽을 수 있는 결과 두 park 뒤에도 flag=true 입니다. 그 결과의 범위 플래그를 지우지 않으므로 wrong-second-wait=false 이며 대기 시간을 측정한 값은 아닙니다. 관측 예제의 before 읽을 수 있는 결과 WAITING 폴링 뒤 상태를 새로 읽습니다. 그 결과의 범위 새 조회도 WAITING이라고 고정할 수 없습니다. 관측 예제의 작업자 로그 읽을 수 있는 결과 park 뒤 isInterrupted() 를 읽습니다. 그 결과의 범위 임의 반환이 앞서면 false일 수 있고 before 로그와의 순서도 바뀔 수 있습니다. 관측 예제의 after 읽을 수 있는 결과 성공한 join 뒤 상태를 읽습니다. 그 결과의 범위 이 지점까지 완료했을 때는 TERMINATED입니다. entered 래치는 park 호출보다 먼저 해제됩니다. main이 WAITING 순간을 놓치면, 작업자가 임의 반환 후 종료했어도 상태 폴링에서 계속 기다릴 수 있습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 독립 시작 조건 자기 요청 프로그램은 플래그를 세우고 상태 관측 프로그램은 entered를 알립니다. |
|
|
| 대기와 조회 범위 각 독립 프로그램의 park와 관측을 같은 시점에서 비교합니다. |
|
|
| 후속 확인 각 프로그램이 확인할 수 있는 마지막 사실을 비교합니다. |
|
|
parkNanos 최대 대기 예산
parkNanos(nanos)는 허가증이 없고 interrupt도 없다면 지정 시간까지 대기할 수 있습니다.
스케줄러 지연 때문에 정확히 그 시각에 실행된다는 보장은 없고 임의 return도 가능합니다.
기한 반복문에서 남은 나노초를 다시 계산합니다.
import java.time.Duration;
import java.util.concurrent.locks.LockSupport;
public final class ParkNanosDeadline {
private static final class ReadyFlag {
volatile boolean ready;
}
public static void main(String[] args) throws InterruptedException {
ReadyFlag flag = new ReadyFlag();
Thread waiter =
new Thread(
() -> System.out.println("outcome=" + await(flag, Duration.ofMillis(30))),
"timed-parker");
waiter.start();
waiter.join();
}
private static String await(ReadyFlag flag, Duration timeout) {
long deadline = System.nanoTime() + timeout.toNanos();
while (!flag.ready) {
if (Thread.currentThread().isInterrupted()) return "interrupted";
long remaining = deadline - System.nanoTime();
if (remaining <= 0) return "timed-out";
LockSupport.parkNanos(remaining);
}
return "ready";
}
}신호 없이 실행하므로 시간 초과를 출력합니다.
준비 상태를 true로 바꾸는 생산자는 flag.ready = true로 값을 공개하고, 대기자 Thread 참조를 보관해 unpark()도 호출해야 빠르게 깨어납니다.
여기서는 volatile 필드가 상태 공개를 담당합니다.
BLOCKED와 WAITING
BLOCKED는 내장 모니터를 얻기 위해 기다리는 상태입니다.
synchronized(lock) 소유자가 나가야 진입 후보가 됩니다.
interrupt를 보내도 모니터 획득을 포기하지 않습니다.
InterruptedException도 발생하지 않습니다.
park는 이미 보유한 모니터를 풀지 않습니다. synchronized(lock) 안에서 park한 스레드는 그 lock을 계속 소유합니다. 깨워 줄 다른 스레드가 같은 lock에 들어가 준비 조건을 바꿔야 한다면 서로 진행하지 못할 수 있습니다. Object.wait는 호출한 객체의 모니터를 내려놓고, 반환 전에 다시 획득한다는 점이 다릅니다. wait도 다른 객체의 락까지 모두 내놓지는 않습니다. 대기 상태 이름만 보지 말고 대기 중에 어떤 락을 소유하는지 확인하세요.
WAITING은 Object.wait, Thread.join, LockSupport.park처럼 사건을 기다리는 상태입니다.
park는 unpark·interrupt·임의 반환, Object.wait는 notify·interrupt, join은 대상 종료·interrupt로 재개합니다.
ReentrantLock의 대기 큐도 내부에서 park를 사용하므로 WAITING으로 보일 수 있습니다.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.LockSupport;
public final class BlockedAndWaitingComparison {
public static void main(String[] args) throws InterruptedException {
Object monitor = new Object();
CountDownLatch enteredPark = new CountDownLatch(1);
Thread waiting =
new Thread(
() -> {
enteredPark.countDown();
LockSupport.park();
},
"plain-waiting");
Thread blocked =
new Thread(
() -> {
synchronized (monitor) {
System.out.println("monitor-acquired");
}
},
"monitor-blocked");
waiting.start();
enteredPark.await();
synchronized (monitor) {
blocked.start();
awaitState(waiting, Thread.State.WAITING);
awaitState(blocked, Thread.State.BLOCKED);
System.out.println("states=" + waiting.getState() + "," + blocked.getState());
blocked.interrupt();
System.out.println("blocked-after-interrupt=" + blocked.getState());
}
LockSupport.unpark(waiting);
waiting.join();
blocked.join();
}
private static void awaitState(Thread t, Thread.State state) throws InterruptedException {
for (int i = 0; i < 200 && t.getState() != state; i++) {
Thread.sleep(1);
}
if (t.getState() != state) throw new IllegalStateException(t.getName());
}
}같은 interrupt라도 모니터 진입 대기를 해제하지 않는다
blocked interrupt는 모니터를 획득시키지 않고, 별도 parked worker는 다른 unpark로 깨웁니다. 요청·실제 진행 조건 해제·join을 단계로 구분해 대기 해제의 조건을 확인할 수 있습니다.
BlockedAndWaitingComparison의 main 모니터 보유 구간, blocked 인터럽트, 모니터 해제 뒤 정리를 두 작업자로 비교합니다.
대기 비교 · 모니터 / PARK
같은 interrupt라도 모니터 진입 대기를 해제하지 않는다
BLOCKED 작업자는 모니터가 풀려야 진입합니다. 다른 작업자의 park 상태 조회는 순간 관측입니다.
blocked 작업자
- 상태 관측 성공·main이 모니터 보유
- 모니터를 얻지 못해 BLOCKED입니다.
- 같은 블록 안의 interrupt
- 인터럽트 후 조회도 BLOCKED입니다.
- 모니터 해제 뒤 정리
- 모니터를 얻어
monitor-acquired를 출력합니다.
waiting 작업자
- 상태 관측 성공·main이 모니터 보유
- WAITING 관측은 순간값이며 이후 상태를 고정하지 않습니다.
- 같은 블록 안의 interrupt
- 이 interrupt의 대상이 아닙니다.
- 모니터 해제 뒤 정리
- main이 unpark를 요청한 뒤 두 작업자를 join합니다.
waiting의 park는 임의로 반환할 수 있습니다. 상태 관측 도우미가 실패하면 뒤의 unpark와 join을 건너뛰므로 대기자가 남아 JVM 종료를 막을 수도 있습니다.
이 예의 조건
- 원 WAITING 관측이 성공한 경로를 추적합니다. park 임의 반환은 가능합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | interrupt 대상은 blocked이며 main의 모니터 반납 전 진입을 포기하지 않습니다. 별도 waiting 작업자는 해제 뒤 unpark로 정리하며 원래 관측 실패와 임의 반환의 한계를 보존합니다. |
| 16 | unpark |
| 17 | 두 join으로 회수 |
| 19 | 관측 실패 시 원 정리가 빠질 수 있음 |
| 구분 | 내용 |
|---|---|
| 설명 | interrupt 대상은 blocked이며 main의 모니터 반납 전 진입을 포기하지 않습니다. 별도 waiting 작업자는 해제 뒤 unpark로 정리하며 원래 관측 실패와 임의 반환의 한계를 보존합니다. |
| 전체 읽기 정보 | 같은 interrupt라도 모니터 진입 대기를 해제하지 않는다 같은 interrupt라도 모니터 진입 대기를 해제하지 않는다 BlockedAndWaitingComparison의 main 모니터 보유 구간, blocked 인터럽트, 모니터 해제 뒤 정리를 두 작업자로 비교합니다. 대기 비교 · 모니터 / PARK 같은 interrupt라도 모니터 진입 대기를 해제하지 않는다 BLOCKED 작업자는 모니터가 풀려야 진입합니다. 다른 작업자의 park 상태 조회는 순간 관측입니다. blocked 작업자 상태 관측 성공·main이 모니터 보유 모니터를 얻지 못해 BLOCKED입니다. 같은 블록 안의 interrupt 인터럽트 후 조회도 BLOCKED입니다. 모니터 해제 뒤 정리 모니터를 얻어 monitor-acquired 를 출력합니다. waiting 작업자 상태 관측 성공·main이 모니터 보유 WAITING 관측은 순간값이며 이후 상태를 고정하지 않습니다. 같은 블록 안의 interrupt 이 interrupt의 대상이 아닙니다. 모니터 해제 뒤 정리 main이 unpark를 요청한 뒤 두 작업자를 join합니다. waiting의 park는 임의로 반환할 수 있습니다. 상태 관측 도우미가 실패하면 뒤의 unpark와 join을 건너뛰므로 대기자가 남아 JVM 종료를 막을 수도 있습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 서로 다른 대기 main은 monitor를 보유하고 blocked는 진입을 기다립니다. |
|
|
| blocked 중단 blocked에 interrupt를 요청해도 main 보유 중 진입 대기를 포기하지 않습니다. |
|
|
| 반납과 회수 main이 monitor를 반납하고 waiting을 unpark합니다. |
|
|
같은 interrupt라도 모니터 진입 대기를 해제하지 않는다
blocked interrupt는 모니터를 획득시키지 않고, 별도 parked worker는 다른 unpark로 깨웁니다. 요청·실제 진행 조건 해제·join을 단계로 구분해 대기 해제의 조건을 확인할 수 있습니다.
BlockedAndWaitingComparison의 main 모니터 보유 구간, blocked 인터럽트, 모니터 해제 뒤 정리를 두 작업자로 비교합니다.
대기 비교 · 모니터 / PARK
같은 interrupt라도 모니터 진입 대기를 해제하지 않는다
BLOCKED 작업자는 모니터가 풀려야 진입합니다. 다른 작업자의 park 상태 조회는 순간 관측입니다.
blocked 작업자
- 상태 관측 성공·main이 모니터 보유
- 모니터를 얻지 못해 BLOCKED입니다.
- 같은 블록 안의 interrupt
- 인터럽트 후 조회도 BLOCKED입니다.
- 모니터 해제 뒤 정리
- 모니터를 얻어
monitor-acquired를 출력합니다.
waiting 작업자
- 상태 관측 성공·main이 모니터 보유
- WAITING 관측은 순간값이며 이후 상태를 고정하지 않습니다.
- 같은 블록 안의 interrupt
- 이 interrupt의 대상이 아닙니다.
- 모니터 해제 뒤 정리
- main이 unpark를 요청한 뒤 두 작업자를 join합니다.
waiting의 park는 임의로 반환할 수 있습니다. 상태 관측 도우미가 실패하면 뒤의 unpark와 join을 건너뛰므로 대기자가 남아 JVM 종료를 막을 수도 있습니다.
이 예의 조건
- 원 WAITING 관측이 성공한 경로를 추적합니다. park 임의 반환은 가능합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | interrupt 대상은 blocked이며 main의 모니터 반납 전 진입을 포기하지 않습니다. 별도 waiting 작업자는 해제 뒤 unpark로 정리하며 원래 관측 실패와 임의 반환의 한계를 보존합니다. |
| 16 | unpark |
| 17 | 두 join으로 회수 |
| 19 | 관측 실패 시 원 정리가 빠질 수 있음 |
| 구분 | 내용 |
|---|---|
| 설명 | interrupt 대상은 blocked이며 main의 모니터 반납 전 진입을 포기하지 않습니다. 별도 waiting 작업자는 해제 뒤 unpark로 정리하며 원래 관측 실패와 임의 반환의 한계를 보존합니다. |
| 전체 읽기 정보 | 같은 interrupt라도 모니터 진입 대기를 해제하지 않는다 같은 interrupt라도 모니터 진입 대기를 해제하지 않는다 BlockedAndWaitingComparison의 main 모니터 보유 구간, blocked 인터럽트, 모니터 해제 뒤 정리를 두 작업자로 비교합니다. 대기 비교 · 모니터 / PARK 같은 interrupt라도 모니터 진입 대기를 해제하지 않는다 BLOCKED 작업자는 모니터가 풀려야 진입합니다. 다른 작업자의 park 상태 조회는 순간 관측입니다. blocked 작업자 상태 관측 성공·main이 모니터 보유 모니터를 얻지 못해 BLOCKED입니다. 같은 블록 안의 interrupt 인터럽트 후 조회도 BLOCKED입니다. 모니터 해제 뒤 정리 모니터를 얻어 monitor-acquired 를 출력합니다. waiting 작업자 상태 관측 성공·main이 모니터 보유 WAITING 관측은 순간값이며 이후 상태를 고정하지 않습니다. 같은 블록 안의 interrupt 이 interrupt의 대상이 아닙니다. 모니터 해제 뒤 정리 main이 unpark를 요청한 뒤 두 작업자를 join합니다. waiting의 park는 임의로 반환할 수 있습니다. 상태 관측 도우미가 실패하면 뒤의 unpark와 join을 건너뛰므로 대기자가 남아 JVM 종료를 막을 수도 있습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 서로 다른 대기 main은 monitor를 보유하고 blocked는 진입을 기다립니다. |
|
|
| blocked 중단 blocked에 interrupt를 요청해도 main 보유 중 진입 대기를 포기하지 않습니다. |
|
|
| 반납과 회수 main이 monitor를 반납하고 waiting을 unpark합니다. |
|
|
직접 대기 루프의 네 조건
조건이 true이면 성공, 인터럽트 상태면 취소, 기한이 지났으면 시간 초과이며 그 외에는 다시 park()합니다.
허가증은 신호 전달 수단일 뿐 대기 조건 자체가 아닙니다.
이 순서를 하나의 도우미에 모으면 호출자가 임의 반환을 잘못 처리할 가능성이 줄어듭니다.
parkUntil(epochMillis)는 시스템 시각 기반의 절대 시간이고 시스템 시각 변경의 영향을 받을 수 있습니다.
경과 시간으로 측정하는 시간 제한에는 단조 증가하는 System.nanoTime()과 parkNanos 조합이 적합합니다.
예산은 매번 새로 시작하지 않습니다. 100ms는 100,000,000ns이며, 시작 뒤 32ms가 지났다면 다음 대기에 전달할 상한은 68,000,000ns입니다. 이후 20ms 만에 임의 반환해도 새 100ms가 아니라 남은 48,000,000ns를 다시 계산합니다. 나노초 값은 날짜가 아닌 같은 JVM 안의 차이로 읽으며, 여기처럼 짧은 예산에서 deadline - now를 사용합니다. now >= start + timeout처럼 절대값을 비교하는 변형은 덧셈 오버플로를 따로 고려해야 합니다. 교육용 도우미에 0·음수·매우 큰 timeout을 허용할지도 호출 계약으로 정해야 하며, 아래 별도 ConditionResultChecks는 0보다 크고 1초 이하인 입력만 받습니다.
매우 짧은 남은 시간에는 park 오버헤드와 스케줄러의 시간 단위가 측정값을 지배할 수 있습니다.
API 문서의 상태별 취소 가능성
모니터 BLOCKED를 즉시 interrupt 취소해야 한다면 synchronized 대신 ReentrantLock.lockInterruptibly()를 검토합니다.
시간 제한 획득에는 시간 제한 tryLock이 있습니다.
단순한 락 보호에 취소 요구가 없다면 synchronized가 더 읽기 쉬울 수 있습니다.
park 기반 사용자 정의 동기화 도구는 대기자 큐 제거와 취소된 노드 정리를 직접 책임집니다.
스레드 하나만 대상으로 한 예제가 성공해도 운영 정확성을 증명하지 않습니다.
ParkInterruptObservation의 entered 신호는 park 호출보다 앞입니다. park가 임의 반환해 이미 종료한 작업자를 main이 WAITING까지 기다리면 원 폴링이 끝나지 않을 수 있습니다. BlockedAndWaitingComparison도 관측 실패가 뒤 unpark를 건너뛸 수 있습니다. 실제 조정에는 상태 이름이 아니라 업무 조건을 사용하고, 관측 실패 시에도 finally에서 대기 조건을 해제하고 작업자를 회수하도록 설계해야 합니다. 모니터 진입을 기다리는 작업자를 회수할 때는 먼저 그 모니터를 내놓아야 합니다.
연습 문제
volatile boolean 조건과 대기자 Thread 참조를 사용해 100ms의 대기 예산을 사용하는 게이트를 작성하세요.
open은 조건을 먼저 true로 만든 뒤 unpark해야 합니다.
정답과 반환 원인 판단
import java.util.concurrent.locks.LockSupport;
public final class TimedParkGateSolution {
private volatile boolean open;
private volatile Thread waiter;
String await(long timeoutNanos) {
waiter = Thread.currentThread();
long deadline = System.nanoTime() + timeoutNanos;
while (!open) {
if (Thread.currentThread().isInterrupted()) return "interrupted";
long left = deadline - System.nanoTime();
if (left <= 0) return "timed-out";
LockSupport.parkNanos(this, left);
}
return "opened";
}
void open() {
open = true;
Thread target = waiter;
if (target != null) LockSupport.unpark(target);
}
public static void main(String[] args) throws InterruptedException {
TimedParkGateSolution gate = new TimedParkGateSolution();
Thread worker =
new Thread(() -> System.out.println(gate.await(100_000_000L)), "timed-gate");
worker.start();
while (gate.waiter == null) {
Thread.onSpinWait();
}
gate.open();
worker.join();
}
}반환값은 각 반복의 검사 순서로 결정된다
open→interrupt→remaining 검사 순서와 parkNanos 뒤 재검사가 반환 결과를 정합니다. 현재 검사와 도달하지 않은 조건을 단계로 보여 주며 반환 조건을 확인할 수 있습니다.
ParkNanosDeadline과 TimedParkGateSolution의 조건, 인터럽트, 남은 시간 검사에 따른 반환값을 비교합니다.
기한 검사 · 조건 먼저
반환값은 각 반복의 검사 순서로 결정된다
각 반복은 업무 조건, 인터럽트, 남은 시간 순서로 검사합니다. 모두 해당하지 않으면 parkNanos 뒤 다시 검사합니다.
업무 조건이 true
- 30ms 대기 예제
ready반환- 100ms 게이트 답안
opened반환
조건은 false·인터럽트는 true
- 30ms 대기 예제
interrupted반환- 100ms 게이트 답안
interrupted반환
조건·인터럽트가 false·남은 시간 ≤ 0
- 30ms 대기 예제
timed-out반환- 100ms 게이트 답안
timed-out반환
그 밖에는 parkNanos 뒤 다시 검사합니다. 30ms 원문은 timed-out이고 답안은 opened 또는 timed-out입니다. opened도 100ms 안에 열렸다는 증거는 아니며, 전체 실행 시간은 예산과 같지 않습니다.
이 예의 조건
- 검사 우선순서는 open→interrupt→remaining입니다. 반환 시각 보장은 없습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원래 코드의 검사 우선순서에 따라 열림/중단/시간 만료/parkNanos 재검사로 나뉩니다. 각 분기는 상호 배타적이며 열림 반환은 기한 내 개방 시각을 증명하지 않습니다. |
| 4 | open |
| 5 | interrupt |
| 20 | opened는 기한 내 개방 시각의 증거가 아니며 예산이 정확한 복귀 시각을 보장하지 않음 |
| 구분 | 내용 |
|---|---|
| 설명 | 원래 코드의 검사 우선순서에 따라 열림/중단/시간 만료/parkNanos 재검사로 나뉩니다. 각 분기는 상호 배타적이며 열림 반환은 기한 내 개방 시각을 증명하지 않습니다. |
| 전체 읽기 정보 | 반환값은 각 반복의 검사 순서로 결정된다 반환값은 각 반복의 검사 순서로 결정된다 ParkNanosDeadline과 TimedParkGateSolution의 조건, 인터럽트, 남은 시간 검사에 따른 반환값을 비교합니다. 기한 검사 · 조건 먼저 반환값은 각 반복의 검사 순서로 결정된다 각 반복은 업무 조건, 인터럽트, 남은 시간 순서로 검사합니다. 모두 해당하지 않으면 parkNanos 뒤 다시 검사합니다. 업무 조건이 true 30ms 대기 예제 ready 반환 100ms 게이트 답안 opened 반환 조건은 false·인터럽트는 true 30ms 대기 예제 interrupted 반환 100ms 게이트 답안 interrupted 반환 조건·인터럽트가 false·남은 시간 ≤ 0 30ms 대기 예제 timed-out 반환 100ms 게이트 답안 timed-out 반환 그 밖에는 parkNanos 뒤 다시 검사합니다. 30ms 원문은 timed-out 이고 답안은 opened 또는 timed-out 입니다. opened도 100ms 안에 열렸다는 증거는 아니며, 전체 실행 시간은 예산과 같지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 열림 우선 open=true이면 중단·기한 검사 전에 반환합니다. |
|
|
| 중단 또는 만료 open=false에서 interrupt를 먼저 보고 남은 시간을 검사합니다. |
|
|
| 대기와 반복 나머지 경우 parkNanos 뒤 처음부터 재검사합니다. |
|
|
반환값은 각 반복의 검사 순서로 결정된다
open→interrupt→remaining 검사 순서와 parkNanos 뒤 재검사가 반환 결과를 정합니다. 현재 검사와 도달하지 않은 조건을 단계로 보여 주며 반환 조건을 확인할 수 있습니다.
ParkNanosDeadline과 TimedParkGateSolution의 조건, 인터럽트, 남은 시간 검사에 따른 반환값을 비교합니다.
기한 검사 · 조건 먼저
반환값은 각 반복의 검사 순서로 결정된다
각 반복은 업무 조건, 인터럽트, 남은 시간 순서로 검사합니다. 모두 해당하지 않으면 parkNanos 뒤 다시 검사합니다.
업무 조건이 true
- 30ms 대기 예제
ready반환- 100ms 게이트 답안
opened반환
조건은 false·인터럽트는 true
- 30ms 대기 예제
interrupted반환- 100ms 게이트 답안
interrupted반환
조건·인터럽트가 false·남은 시간 ≤ 0
- 30ms 대기 예제
timed-out반환- 100ms 게이트 답안
timed-out반환
그 밖에는 parkNanos 뒤 다시 검사합니다. 30ms 원문은 timed-out이고 답안은 opened 또는 timed-out입니다. opened도 100ms 안에 열렸다는 증거는 아니며, 전체 실행 시간은 예산과 같지 않습니다.
이 예의 조건
- 검사 우선순서는 open→interrupt→remaining입니다. 반환 시각 보장은 없습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 원래 코드의 검사 우선순서에 따라 열림/중단/시간 만료/parkNanos 재검사로 나뉩니다. 각 분기는 상호 배타적이며 열림 반환은 기한 내 개방 시각을 증명하지 않습니다. |
| 4 | open |
| 5 | interrupt |
| 20 | opened는 기한 내 개방 시각의 증거가 아니며 예산이 정확한 복귀 시각을 보장하지 않음 |
| 구분 | 내용 |
|---|---|
| 설명 | 원래 코드의 검사 우선순서에 따라 열림/중단/시간 만료/parkNanos 재검사로 나뉩니다. 각 분기는 상호 배타적이며 열림 반환은 기한 내 개방 시각을 증명하지 않습니다. |
| 전체 읽기 정보 | 반환값은 각 반복의 검사 순서로 결정된다 반환값은 각 반복의 검사 순서로 결정된다 ParkNanosDeadline과 TimedParkGateSolution의 조건, 인터럽트, 남은 시간 검사에 따른 반환값을 비교합니다. 기한 검사 · 조건 먼저 반환값은 각 반복의 검사 순서로 결정된다 각 반복은 업무 조건, 인터럽트, 남은 시간 순서로 검사합니다. 모두 해당하지 않으면 parkNanos 뒤 다시 검사합니다. 업무 조건이 true 30ms 대기 예제 ready 반환 100ms 게이트 답안 opened 반환 조건은 false·인터럽트는 true 30ms 대기 예제 interrupted 반환 100ms 게이트 답안 interrupted 반환 조건·인터럽트가 false·남은 시간 ≤ 0 30ms 대기 예제 timed-out 반환 100ms 게이트 답안 timed-out 반환 그 밖에는 parkNanos 뒤 다시 검사합니다. 30ms 원문은 timed-out 이고 답안은 opened 또는 timed-out 입니다. opened도 100ms 안에 열렸다는 증거는 아니며, 전체 실행 시간은 예산과 같지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 열림 우선 open=true이면 중단·기한 검사 전에 반환합니다. |
|
|
| 중단 또는 만료 open=false에서 interrupt를 먼저 보고 남은 시간을 검사합니다. |
|
|
| 대기와 반복 나머지 경우 parkNanos 뒤 처음부터 재검사합니다. |
|
|
이미 시간 초과로 반환한 호출의 결과는 나중의 open으로 바뀌지 않습니다.
예시
다음 별도 프로그램은 상태 폴링 없이 조건·중단·기한의 세 반환을 확인합니다. 앞에서 연 게이트, 열지 않은 새 게이트, 중단 플래그가 있는 호출을 각각 독립 입력으로 사용합니다.
import java.util.concurrent.locks.LockSupport;
public final class ConditionResultChecks {
private static final class Gate {
private volatile boolean open;
private volatile Thread waiter;
void open() {
open = true;
Thread target = waiter;
if (target != null) LockSupport.unpark(target);
}
String await(long budgetNanos) {
if (budgetNanos <= 0 || budgetNanos > 1_000_000_000L)
throw new IllegalArgumentException("budget");
waiter = Thread.currentThread();
long deadline = System.nanoTime() + budgetNanos;
while (!open) {
if (Thread.currentThread().isInterrupted()) return "interrupted";
long remaining = deadline - System.nanoTime();
if (remaining <= 0) return "timed-out";
LockSupport.parkNanos(this, remaining);
}
return "opened";
}
}
public static void main(String[] args) {
Gate early = new Gate();
early.open();
System.out.println("early=" + early.await(50_000_000L));
System.out.println("closed=" + new Gate().await(1_000_000L));
Thread.currentThread().interrupt();
System.out.println("cancelled=" + new Gate().await(50_000_000L));
System.out.println("flag-kept=" + Thread.currentThread().isInterrupted());
}
}출력은 early=opened, closed=timed-out, cancelled=interrupted, flag-kept=true입니다. 이 코드는 현재 호출자에서 분기 순서를 확인하며 다른 스레드의 WAITING 상태를 재현하지 않습니다. 이미 열린 조건이면 기한이나 중단 검사를 하지 않고 성공하므로, 취소를 무조건 우선할 업무라면 검사 순서도 새 계약으로 정해야 합니다. 1ms는 park에 전달하는 예산이며 정확한 전체 실행 시간이 아닙니다.
park 계열을 사용할 때는 interrupt가 플래그를 지우지 않는다는 규칙, nanos 기한 재계산, 상태별 취소 가능성을 함께 다뤄야 합니다.
BLOCKED와 WAITING을 같은 “멈춤”으로 보면 적절한 해결 API를 고를 수 없습니다.
추가 연습 — 반환 우선순서와 대기 예산을 검산합니다. (1) TimedParkGateSolution의 새 인스턴스를 같은 호출자에서 검사합니다. A는 먼저 open하고 호출자의 인터럽트 플래그를 true로 만든 뒤 await(0)을 부릅니다. B는 닫힌 새 인스턴스에 같은 true 플래그로 await(0)을 부릅니다. C는 닫힌 새 인스턴스에 플래그 false로 await(0)을 부릅니다. 각 반환값과 플래그를 구하세요. 이 질문의 0은 원 답안 도우미의 입력이며, 양수 입력만 허용하는 별도 ConditionResultChecks와 구분합니다. (2) 100ms 예산에서 32ms, 이어 20ms가 경과하고 조건과 인터럽트가 모두 false입니다. 각 재검사 때 남은 ns를 계산하고, 또 48ms가 경과했다면 반환을 구하세요. 각 경과는 가정한 관측값이며 정확한 스케줄링 약속이 아닙니다. (3) 작업자 A가 synchronized(lock) 안에서 park하고, 작업자 B가 같은 lock 안에서 ready=true를 쓰고 A를 unpark하려 합니다. 다른 반환 원인이 없다고 가정할 때 왜 멈추며, 관측 실험의 main이 같은 모니터를 가진 채 blocked 작업자를 join하는 정리도 왜 진행하지 못하나요?
추가 연습의 풀이 확인
- A는 open 조건을 먼저 읽어 opened를 반환하고 플래그 true를 유지합니다. B는 open=false 뒤 인터럽트를 먼저 검사해 interrupted를 반환하며 true를 유지합니다. C는 중단도 없어 남은 시간이 0 이하임을 확인해 timed-out을 반환하고 false를 유지합니다. 원 답안은 isInterrupted를 쓰므로 플래그를 지우지 않습니다. 반환 순서는 open → interrupt → remaining이며, 0 입력을 거부하는 다른 도우미의 계약으로 바꾸면 이 세 호출 자체가 유효하지 않을 수 있습니다.
- 각각 (100−32)×1,000,000=68,000,000ns와 (100−32−20)×1,000,000=48,000,000ns입니다. 마지막 48ms 뒤에는 0 이하라 timed-out입니다. 반복마다 100ms를 새로 주면 임의 반환 횟수만큼 총 예산을 늘리게 됩니다. 실제 반환은 다시 실행될 스케줄링을 필요로 하므로 지정한 예산과 전체 벽시계 실행 시간을 동일시하지 않습니다.
- A가 park해도 lock을 내놓지 않아 B가 ready 쓰기와 unpark에 도달하지 못합니다. 조건을 확인한 뒤 락 밖에서 park할 수 있는 올바른 공개·등록 절차나 적합한 상위 대기 도구가 필요합니다. main이 모니터를 가진 채 blocked 작업자를 join하면 그 작업자의 진입·종료를 main 스스로 막습니다. 모니터 보유 구간을 끝낸 뒤 필요한 신호를 보내고 join하는 순서로 회수해야 합니다.