본문으로 건너뛰기
김신건의 로그

[Java] ConcurrentLinkedQueue

· 수정 · 📖 약 1분 · 398자/단어 #java #concurrent #queue #lock-free #michael-scott
ConcurrentLinkedQueue, java.util.concurrent.ConcurrentLinkedQueue, CLQ, lock-free queue

정의

java.util.concurrent.ConcurrentLinkedQueue<E>lock-free 로 구현된 unbounded thread-safe Queue. Michael & Scott 의 non-blocking queue 알고리즘 (1996) 기반.

BlockingQueue 가 아닌 단순 큐. 큐가 비어 있으면 poll() 이 즉시 null 반환 (block 하지 않음).

시각화

내부 구조

public class ConcurrentLinkedQueue<E> ... {
    private transient volatile Node<E> head;
    private transient volatile Node<E> tail;

    static final class Node<E> {
        volatile E item;
        volatile Node<E> next;
    }
}

head, tail, item, next 모두 volatile. 모든 갱신이 volatile write/CAS 로 수행돼 lock-free.

offer 의 흐름 (Michael & Scott)

offer(x):
  newNode = Node(x)
  loop:
    t = tail
    next = t.next
    if (next == null) {
      // tail 의 next 가 비어있음, 여기에 CAS
      if (CAS(t.next, null, newNode)) {
        CAS(tail, t, newNode)         // tail 이동 (best-effort)
        return
      }
    } else {
      // 다른 스레드가 추가했지만 tail 이 아직 안 옮겨짐
      CAS(tail, t, next)               // 도와서 tail 이동
    }

핵심: tail 이동이 best-effort, 즉 다른 스레드가 도와서 옮길 수 있다. 따라서 어떤 스레드든 큐의 일관성을 유지.

poll 의 흐름

poll():
  loop:
    h = head
    next = h.next
    if (next == null) return null     // 비어 있음
    if (CAS(head, h, next)) {
      e = next.item
      next.item = null                // GC 친화
      return e
    }

복잡도

작업시간동시성
offer(e)amortized O(1)lock-free, CAS 재시도
poll()amortized O(1)lock-free, CAS 재시도
peek()O(1)volatile read
size()O(n)약함, 정확하지 않을 수 있음
containsO(n)약함
순회O(n)weakly consistent

IMPORTANT

size() 가 O(n) 이고 부정확하다. 동시 수정 중에는 정확한 카운트 보장 안 됨. 정확한 개수가 필요하면 외부 카운터.

BlockingQueue 와의 차이

항목ConcurrentLinkedQueueBlockingQueue (LinkedBlockingQueue 등)
비어 있을 때 poll즉시 null(take 는) block
가득 차 있을 때 offer(unbounded 라 발생 X)(put 은) block
lock-free (CAS)ReentrantLock
백프레셔✗ (unbounded)
메모리 보호✗ (OOM 가능)✓ (bounded 설정 가능)

생산자-소비자 같은 패턴에는 BlockingQueue 가 보통 더 적합. CLQ 는 polling 기반 처리, 비어 있을 때 다른 일 하는 패턴에 적합.

사용 예

작업 큐 (polling)

Queue<Task> queue = new ConcurrentLinkedQueue<>();

// Producer
queue.offer(new Task());

// Consumer (polling)
while (running) {
    Task t = queue.poll();
    if (t == null) {
        Thread.sleep(10);    // 잠시 쉬고 다시
        continue;
    }
    process(t);
}

비어 있을 때 즉시 반환되므로 polling 으로 직접 backoff 가능.

참고

이 글의 용어 (8개)
[Java] BlockingQueuejava
정의 는 요소를 가져올 때 비어 있으면 대기, 넣을 때 가득 차 있으면 대기 하는 thread-safe 큐 인터페이스. 생산자-소비자 (producer-consumer) 패턴의 …
[Java] Collectionjava
정의 는 그룹으로 묶인 객체들을 표현하는 최상위 인터페이스. JCF (Java Collections Framework) 의 입구이자, / / / 모두 이를 확장한다. 자체는 직접…
[Java] ConcurrentLinkedDequejava
정의 는 lock-free . 의 양방향 버전. JDK 1.7 추가. 양 끝 모두에서 add/remove 가능, 모든 연산이 CAS 기반. 내부 구조 이중 연결 리스트 ( , 모…
[Java] Iterablejava
정의 는 루프로 순회 가능한 모든 타입의 최상위 인터페이스. 단 하나의 추상 메서드, 를 정의한다. 인터페이스가 을 extends 하므로 , , , 등 모든 컬렉션이 자동으로 대…
[Java] Objectjava
정의 는 Java 의 모든 클래스의 최상위 부모 (root) 클래스. 가 명시되지 않은 클래스는 컴파일러가 자동으로 를 붙인다. 인터페이스는 클래스가 아니라 를 직접 상속하지는 …
[Java] volatilejava
정의 은 Java 의 키워드. 필드에 붙이면 두 가지를 보장한다. 1. 가시성 (visibility): 한 스레드의 쓰기가 다른 모든 스레드에 즉시 보인다. CPU 캐시에 머무르…
큐 (Queue)algorithm
정의 큐 (Queue) 는 FIFO (First In, First Out) 순서로 원소를 관리하는 추상 자료구조입니다. (또는 ), (또는 ), 세 연산만 제공하며, 가장 먼저 …
Non-Blocking (논블로킹)concurrency
정의 Non-Blocking 은 호출이 결과를 기다리지 않고 즉시 반환 하는 실행 방식. 결과가 준비되지 않았다면 "아직 안 됐다" 라는 신호 ( , , , 등) 를 반환하고, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기