The previous BlockingQueue is a one-way FIFO queue, while LinkedBlockingDeque is a two-way blocking queue composed of linked lists. A two-way queue means that elements can be inserted and removed from both ends of the header and tail. It also means that LinkedBlockingDeque supports FIFO and FILO operations.
LinkedBlockingDeque is an optional capacity. You can set the capacity to prevent excessive expansion during initialization. If it is not set, the default capacity size is Integer.MAX_VALUE.
LinkedBlockingDeque
LinkedBlockingDeque inherits AbstractQueue and implements the interface BlockingDeque, and BlockingDeque inherits the interface BlockingQueue. BlockingDeque is a Queue that supports two additional operations: wait for the double ended Queue to become non empty when obtaining elements; Wait for space in the dual ended Queue to become available while storing elements. These two types of operations make it possible for the two-way operation Queue of LinkedBlockingDeque. The BlockingDeque interface provides a series of methods ending with First and Last, such as addFirst, addLast, peekFirst and peekLast.
public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>, java.io.Serializable { // Header of bidirectional linked list transient Node<E> first; // Tail of bidirectional linked list transient Node<E> last; // Size, number of current nodes in the bidirectional linked list private transient int count; // Capacity, specified when creating LinkedBlockingDeque private final int capacity; final ReentrantLock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); private final Condition notFull = lock.newCondition(); }
It can be seen from the above Lock that the underlying implementation mechanism of LinkedBlockingDeque is the same as that of LinkedBlockingQueue, which is still implemented through the mutex ReentrantLock. The two conditions of notEmpty and notFull coordinate producer and consumer issues.
Like other blockingqueues, nodes still use the internal class Node:
static final class Node<E> { E item; Node<E> prev; Node<E> next; Node(E x) { item = x; } }
Two way, the node must have a precursor prev and a successor next.
Basic method
The add, put, offer, take, peek and poll methods of LinkedBlockingDeque all call the XXXFirst and XXXLast methods. Therefore, only putFirst, putLast, pollFirst and pollLast are used for analysis here.
putFirst
putFirst(E e): inserts the specified element at the beginning of this double ended queue, waiting for free space if necessary.
public void putFirst(E e) throws InterruptedException { // check null if (e == null) throw new NullPointerException(); Node<E> node = new Node<E>(e); // Acquire lock final ReentrantLock lock = this.lock; lock.lock(); try { while (!linkFirst(node)) // Wait on the notFull condition until awakened or interrupted notFull.await(); } finally { // Release lock lock.unlock(); } }
First get the lock, then call the linkFirst method to list, and finally release the lock. If the queue is full, wait on notFull. Linkfirst set the Node as the counterpart:
private boolean linkFirst(Node<E> node) { // Excess capacity if (count >= capacity) return false; // First node Node<E> f = first; // The next of the new node points to the original first node.next = f; // Set node to new first first = node; // No tail node, set node as tail node if (last == null) last = node; // If there is a tail node, point the pre of the previous first to the new node else f.prev = node; ++count; // Wake up notEmpty notEmpty.signal(); return true; }
linkFirst is mainly used to set the column header node of the node queue. It returns true if the queue is successful. If the queue is full, it returns false. The whole process is relatively simple.
putLast
putLast(E e): inserts the specified element at the end of this double ended queue, waiting for free space if necessary.
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 { while (!linkLast(node)) notFull.await(); } finally { lock.unlock(); } }
Call linkLast to link the Node to the end of the queue:
private boolean linkLast(Node<E> node) { if (count >= capacity) return false; // Tail node Node<E> l = last; // Point the precursor of the Node to the original last node.prev = l; // Set node to last last = node; // If the first node is null, set node to first if (first == null) first = node; else //If it is not null, it means that the previous last has a value, then point the next of the previous last to node l.next = node; ++count; notEmpty.signal(); return true; }
pollFirst
pollFirst(): get and remove the first element of this double ended queue; null if this double ended queue is empty.
public E pollFirst() { final ReentrantLock lock = this.lock; lock.lock(); try { return unlinkFirst(); } finally { lock.unlock(); } }
Call unlinkFirst to remove the first element of the queue:
private E unlinkFirst() { // First node Node<E> f = first; // Empty queue, directly return null if (f == null) return null; // first.next Node<E> n = f.next; // Node item E item = f.item; // Remove first = = > first = first.next f.item = null; f.next = f; // help GC first = n; // Empty queue after removal, with only one node if (n == null) last = null; else // N's pre used to point to the previous first. Now n has become first, and pre points to null n.prev = null; --count; notFull.signal(); return item; }
pollLast
pollLast(): get and remove the last element of the double ended queue; null if this double ended queue is empty.
public E pollLast() { final ReentrantLock lock = this.lock; lock.lock(); try { return unlinkLast(); } finally { lock.unlock(); } }
Call unliklast to remove the tail node. If the linked list is empty, null will be returned:
private E unlinkLast() { // assert lock.isHeldByCurrentThread(); Node<E> l = last; if (l == null) return null; 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; }
Most methods of LinkedBlockingDeque are implemented through linkFirst, linkLast, unlikfirst and unliklast. Because they are two-way queues, they are all aimed at first and last operations. It is not difficult to understand the whole LinkedBlockingDeque.
After mastering the insertion and deletion of two-way queues, LinkedBlockingDeque has no difficulty. The importance of data structure!!!!