Java blocking queue LinkedBlockingDeque

Keywords: Java data structure

LinkedBlockingDeque

The LinkedBlockingDeque class implements the BlockingDeque interface. Read the BlockingDeque text for more information about.

Deque comes from the word "double ended queue". Deque is a queue where you can insert and delete elements at both ends of the queue.

LinkedBlockingDeque is a Deque. If a thread tries to get an element from it and the queue is empty, it will be blocked no matter which end the thread tries to get an element from.

The following is an example of instantiating and using LinkedBlockingDeque:

BlockingDeque<String> deque = new LinkedBlockingDeque<String>();

deque.addFirst("1");
deque.addLast("2");

String two = deque.takeLast();
String one = deque.takeFirst();

Source code

The implementation of LinkedBlockingDeque is basically similar to LinkedBlockingQueue, except that LinkedBlockingDeque provides more operations. And LinkedBlockingQueue has two built-in locks for put and take operations respectively, while LinkedBlockingDeque uses only one lock to control all operations. Because the queue can carry out put and take operations at the head and tail at the same time, it is necessary to lock the two locks at the same time to ensure the synchronization of operations. It is not as good as using only one lock.

The synchronization node has one more prev field than LinkedBlockingQueue.

static final class Node<E> {
    E item;

    Node<E> prev;

    Node<E> next;

    Node(E x) {
        item = x;
    }
}

Add operation

Compared with LinkedBlockingQueue, the addition operation can only be added at the end of the queue. It can be added at both ends of the queue.

public void addFirst(E e) {
    // Multiplexing offer method
    if (!offerFirst(e))
        throw new IllegalStateException("Deque full");
}

public void addLast(E e) {
    if (!offerLast(e))
        throw new IllegalStateException("Deque full");
}

public boolean offerFirst(E e) {
    if (e == null) throw new NullPointerException();
    // Construction node
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // Insert into queue header
        return linkFirst(node);
    } finally {
        lock.unlock();
    }
}

private boolean linkFirst(Node<E> node) {
    // assert lock.isHeldByCurrentThread();

    // Returns false if the queue is full
    if (count >= capacity)
        return false;
    // Get the header node, point your next field to the header node, and then set yourself as the header node
    Node<E> f = first;
    node.next = f;
    first = node;
    // If the queue is empty, the tail node also points to itself
    if (last == null)
        last = node;
    else
        f.prev = node;
    ++count;
    // Wake up the thread waiting to get the element
    notEmpty.signal();
    return true;
}

public boolean offerLast(E e) {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // Insert at the end of the queue
        return linkLast(node);
    } finally {
        lock.unlock();
    }
}

private boolean linkLast(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    
    // Returns false if the queue is full
    if (count >= capacity)
        return false;
    // Set yourself as the tail node
    Node<E> l = last;
    node.prev = l;
    last = node;
    // If the queue is empty, the header node also points to itself
    if (first == null)
        first = node;
    else
        l.next = node;
    ++count;
    // Wake up the thread waiting to get the element
    notEmpty.signal();
    return true;
}

public void putFirst(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // If the queue is full, wait
        while (!linkFirst(node))
            notFull.await();
    } finally {
        lock.unlock();
    }
}

public void putLast(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // If the queue is full, wait
        while (!linkLast(node))
            notFull.await();
    } finally {
        lock.unlock();
    }
}

public boolean offerFirst(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    // Calculate timeout
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // If the queue is full, wait for a timeout
        while (!linkFirst(node)) {
            if (nanos <= 0L)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        return true;
    } finally {
        lock.unlock();
    }
}

public boolean offerLast(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (e == null) throw new NullPointerException();
    Node<E> node = new Node<E>(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (!linkLast(node)) {
            if (nanos <= 0L)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        return true;
    } finally {
        lock.unlock();
    }
}

Delete operation

public E removeFirst() {
    // Multiplex poll operation
    E x = pollFirst();
    if (x == null) throw new NoSuchElementException();
    return x;
}

public E removeLast() {
    E x = pollLast();
    if (x == null) throw new NoSuchElementException();
    return x;
}

public E pollFirst() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // Gets the value of the header node and deletes it
        return unlinkFirst();
    } finally {
        lock.unlock();
    }
}

private E unlinkFirst() {
    // assert lock.isHeldByCurrentThread();

    // Returns null if the queue is empty
    Node<E> f = first;
    if (f == null)
        return null;
    // Reset header node
    Node<E> n = f.next;
    E item = f.item;
    f.item = null;
    f.next = f; // help GC
    first = n;
    if (n == null)
        last = null;
    else
        n.prev = null;
    --count;
    // Wake up threads waiting to be inserted
    notFull.signal();
    return item;
}

public E pollLast() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return unlinkLast();
    } finally {
        lock.unlock();
    }
}

private E unlinkLast() {
    // assert lock.isHeldByCurrentThread();
    Node<E> l = last;
    // The queue is empty and null is returned
    if (l == null)
        return null;
    // Update tail node
    Node<E> p = l.prev;
    E item = l.item;
    l.item = null;
    l.prev = l; // help GC
    last = p;
    if (p == null)
        first = null;
    else
        p.next = null;
    --count;
    notFull.signal();
    return item;
}

public E takeFirst() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        // If the queue is empty, wait
        while ( (x = unlinkFirst()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}

public E takeLast() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        // If the queue is empty, wait
        while ( (x = unlinkLast()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}

public E pollFirst(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        E x;
        while ( (x = unlinkFirst()) == null) {
            if (nanos <= 0L)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return x;
    } finally {
        lock.unlock();
    }
}

public E pollLast(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        E x;
        while ( (x = unlinkLast()) == null) {
            if (nanos <= 0L)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return x;
    } finally {
        lock.unlock();
    }
}

Access operation

public E getFirst() {
    // Multiplex peek method
    E x = peekFirst();
    if (x == null) throw new NoSuchElementException();
    return x;
}

public E getLast() {
    E x = peekLast();
    if (x == null) throw new NoSuchElementException();
    return x;
}

public E peekFirst() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // If the queue is not empty, the header element is returned
        return (first == null) ? null : first.item;
    } finally {
        lock.unlock();
    }
}

public E peekLast() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // If the queue is not empty, the tail element is returned
        return (last == null) ? null : last.item;
    } finally {
        lock.unlock();
    }
}

BlockingQueue

Since BlockingDeque inherits from the BlockingQueue interface, you need to implement the methods in BlockingQueue. Specifically, you only need to reuse the methods mentioned above.

public boolean add(E e) {
    addLast(e);
    return true;
}

public boolean offer(E e) {
    return offerLast(e);
}

public void put(E e) throws InterruptedException {
    putLast(e);
}

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    return offerLast(e, timeout, unit);
}

public E remove() {
    return removeFirst();
}

public E poll() {
    return pollFirst();
}

public E take() throws InterruptedException {
    return takeFirst();
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    return pollFirst(timeout, unit);
}

public E element() {
    return getFirst();
}

public E peek() {
    return peekFirst();
}

Core points

  • LinkedBlockingDeque is a double ended blocking queue based on linked list. It is thread safe. Elements are not allowed to be null
  • A two-way linked list is used internally
  • put and take operations can be performed simultaneously at both ends of the linked list. Only one lock can be used
  • After the insert thread completes the operation, if the queue is not full, it will wake up other threads waiting to be inserted. At the same time, if the queue is not empty, it will wake up the threads waiting to obtain elements; The same is true for take threads.
  • The iterator maintains weak consistency with the internal two-way linked list. After calling the remove(T) method to delete an element, it will not release its next reference to the next node, otherwise the iterator will not work.
  • The foreachremaining (consumer <? Super E > action) of the iterator operates in a batch of 64 elements
  • Foreach (consumer <? Super E > action), removeIf, removeAll, and retainAll all operate in a batch of 64 elements

Posted by macmonkey on Mon, 18 Oct 2021 19:02:47 -0700