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