Java BlockingQueue: 블로킹 연산으로 스레드 안전한 큐 구현
Java BlockingQueue
Java BlockingQueue 인터페이스, 구현체, 메서드 분류, 실전 활용법을 탐구합니다.
BlockingQueue 인터페이스는 Java 컬렉션 프레임워크의 일부로 Queue를 확장하며 블로킹 동작을 추가합니다. 삽입과 제거 같은 연산은 큐 상태가 허용할 때까지 자동으로 대기합니다.
예를 들어, 빈 큐에서 요소를 제거하려 하면 아이템이 사용 가능해질 때까지 호출 스레드를 블로킹하여 생산자와 소비자 간 안전한 조정을 보장합니다.
구체 구현체
BlockingQueue는 인터페이스이므로 구체 클래스가 실제 기능을 제공합니다:
ArrayBlockingQueue – 배열 기반 고정 크기 FIFO 큐
LinkedBlockingQueue – 선택적 고정 크기 연결 리스트 구현
BlockingQueue 사용하기
인스턴스 생성 전 패키지 임포트:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
예제 인스턴스:
// BlockingQueue의 배열 구현
BlockingQueue<String> animal1 = new ArrayBlockingQueue<>(10);
// BlockingQueue의 LinkedList 구현
BlockingQueue<String> animal2 = new LinkedBlockingQueue<>();
두 객체 모두 BlockingQueue 인터페이스의 모든 메서드를 지원합니다.
메서드 분류
BlockingQueue 메서드는 큐가 가득 찼거나 비었을 때 반응 방식에 따라 그룹화됩니다:
예외 발생 메서드
add(E e) – 요소 삽입, 가득 차면 IllegalStateException 발생
element() – 헤드 조회(제거 안 함), 비어있으면 NoSuchElementException 발생
remove() – 헤드 제거, 비어있으면 NoSuchElementException 발생
반환값 기반 메서드
offer(E e) – 요소 삽입, 가득 차면 false 반환
peek() – 헤드 조회, 비어있으면 null 반환
poll() – 헤드 제거, 비어있으면 null 반환
타임아웃 지원 변형
offer와 poll은 타임아웃을 지원합니다:
boolean success = queue.offer(item, 100, TimeUnit.MILLISECONDS);
이 예제에서 메서드는 최대 100밀리초 동안 item 삽입을 시도하고, 타임아웃 시 false 반환. 지원 시간 단위: days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds.
블로킹 메서드
put(E e) – 요소 삽입, 큐가 가득 차면 공간 생길 때까지 블로킹
take() – 헤드 제거 및 반환, 큐가 비어있으면 요소가 삽입될 때까지 블로킹
이 메서드들은 생산자-소비자 패턴에서 스레드가 바쁜 대기 없이 동기화할 때 필수적입니다.
ArrayBlockingQueue 예제
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class Main {
public static void main(String[] args) {
// 용량 5의 고정 크기 블로킹 큐 생성
BlockingQueue<Integer> numbers = new ArrayBlockingQueue<>(5);
try {
// 요소 삽입 – 큐가 가득 차면 블로킹
numbers.put(2);
numbers.put(1);
numbers.put(3);
System.out.println("BlockingQueue: " + numbers);
// 요소 제거 – 큐가 비어있으면 블로킹
int removedNumber = numbers.take();
System.out.println("Removed Number: " + removedNumber);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Operation interrupted: " + e.getMessage());
}
}
}
출력
BlockingQueue: [2, 1, 3] Removed Number: 2
자세한 내용은 공식 Java 문서의 ArrayBlockingQueue를 참조하세요.
왜 BlockingQueue를 사용하는가?
BlockingQueue의 핵심 장점은 본질적 스레드 안전성입니다. 여러 스레드가 데이터를 동시에 생산하고 소비할 때 명시적 동기화가 불필요합니다.
소비자 스레드가 생산자보다 빠르게 아이템을 제거하는 상황을 고려해보세요. 블로킹 동작이 없으면 소비자는 예외를 던지거나 바쁜 대기 루프를 돌게 됩니다. BlockingQueue는 생산자가 새 데이터를 제공할 때까지 소비자를 자동으로 일시정지시키고, 그 반대도 마찬가지입니다.
이러한 내장 블로킹 메커니즘을 활용하면 개발자는 최소한의 보일러플레이트로 견고하고 확장 가능한 동시 애플리케이션을 구축할 수 있습니다.