Java / JVMintermediate검토 2026.08

생산자–소비자와 BlockingQueue

생산 속도와 소비 속도의 차이를 유한 Queue로 흡수하고 과부하를 통제한다.

#BlockingQueue#backpressure#bounded-queue

Overview

생산자–소비자 패턴은 작업을 만드는 쪽과 처리하는 쪽 사이에 Queue를 두어 속도 차이를 흡수한다. 핵심은 Queue를 두는 것 자체가 아니라 가득 찼을 때와 비었을 때의 동작을 정하는 것이다.

왜 필요한가

여러 Thread가 일반 Queue를 동시에 바꾸면 Race Condition이 생길 수 있다. 비어 있거나 가득 찬 Queue를 계속 확인하면 CPU를 쓰는 Busy Waiting이 된다. BlockingQueue는 동시성 제어와 효율적인 대기를 표준화한다.

핵심 동작

  • put: 공간이 생길 때까지 기다린다.
  • take: 항목이 생길 때까지 기다린다.
  • offer: 즉시 실패하거나 지정 시간까지만 기다린다.
  • poll: 즉시 null을 반환하거나 지정 시간까지만 기다린다.
flowchart LR; P[생산자] --> Q[유한 BlockingQueue]; Q --> C[소비자]; Q -. 가득 참 .-> BP[대기·거절·우회]; C -. 느려짐 .-> L[Queue 길이 증가]

Example

BlockingQueue<Job> queue = new ArrayBlockingQueue<>(1000);
 
if (!queue.offer(job, 200, TimeUnit.MILLISECONDS)) {
    throw new RejectedExecutionException("작업 대기열 포화");
}
 
Job next = queue.take();
worker.process(next);

실무에서 발생하는 문제

생산률이 소비율보다 계속 높으면 Queue는 결국 찬다. 무한 Queue는 과부하를 해결하지 않고 메모리 사용량과 대기시간으로 숨긴다. 종료할 때 소비자가 영원히 대기하지 않도록 interrupt, 종료 신호, poison pill 같은 정책도 필요하다.

Trade-off

작은 Queue는 빠르게 거절해 시스템을 보호하지만 순간 부하 흡수력이 낮다. 큰 Queue는 Burst를 흡수하지만 오래된 작업의 지연과 메모리 사용량이 커진다. 대기, 거절, Caller 실행, 별도 저장소 전환 중 어떤 정책이 업무에 맞는지 정한다.

흔한 오해

BlockingQueue는 무한한 처리량을 만들지 않는다. 생산자와 소비자를 안전하게 조율할 뿐이며, 지속적인 용량 초과는 Backpressure나 Load Shedding으로 다뤄야 한다.

Interview Questions

  1. put/takeoffer/poll은 어떤 상황에서 선택하는가?
  2. 무한 Queue가 운영 장애를 늦게 드러내는 이유는 무엇인가?
  3. Thread Pool의 Work Queue와 이 패턴은 어떻게 연결되는가?
  4. Graceful Shutdown 시 대기 중인 소비자를 어떻게 종료할 것인가?

Thread Pool, Kafka, Amazon SQS/SNS로 Queue 경계가 Process 밖으로 이동하는 경우까지 이어서 본다.

SOURCE REFERENCES

이 문서의 근거

본문은 Dev Atlas 안에서 완결되며, 검증이 필요할 때만 원문을 확인할 수 있습니다.

원문 출처 보기 1