Java. util. concurrent package source code reading 07 javasblockingqueue, java. util package
In this article, we will talk about javasblockingqueue, which is a little more complex. LinkedBlockingQueue is implemented using a linked list. A head and tail point to the start and end of the queue respectively. Therefore, LinkedBlockingQueue will have two locks to control the two elements respectively, so that there will be no lock conflicts when adding and removing elements, so the removal of elements is the head, the operation for adding elements is tail.
Old rules first look at the offer method and poll Method
public boolean offer(E e) { if (e == null) throw new NullPointerException(); final AtomicInteger count = this.count; if (count.get() == capacity) return false; int c = -1; Node<E> node = new Node(e); final ReentrantLock putLock = this.putLock; putLock.lock(); try { if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); return c >= 0; }
You can see that the offer method only involves putLock when adding elements, but you still need takeLock. You can see the signalNotEmpty code. When the poll method removes elements, takeLock is involved, and putLock is also required. See signalNotFull (). SignalNotEmpty will talk about blocking later.
public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) return null; E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { if (count.get() > 0) { x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
Here, by the way, we will talk about the count of the queue length. Because there are two locks, if we still use the basic type of count like ArrayBlockingQueue, we will use two locks at the same time, which will be very complicated, therefore, the atomic data type AtomicInteger is used directly to operate the count.
Next, let's talk about blocking. A BlockingQueue has two Condition: notFull and notEmpty. The other BlockingQueue has two locks. Therefore, these two conditions must be created by the two locks respectively. takeLock creates notEmpty, putLock creates notFull.
/** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
Next let's take a look at the put method:
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. int c = -1; Node<E> node = new Node(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { /* * Note that count is used in wait guard even though it is * not protected by lock. This works because count can * only decrease at this point (all other puts are shut * out by lock), and we (or some other waiting put) are * signalled if it ever changes from capacity. Similarly * for all other uses of count in other wait guards. */ while (count.get() == capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); }
In fact, the general logic is similar to that of ArrayBlockingQueue, and notEmpty conditions need to be notified. Because the notEmpty condition belongs to takeLock and the signal method needs to be called to obtain the Lock, the put method also uses another Lock: takeLock. There is a difference here. In principle, the put method does not need to notify the notFull condition. It is notified by the operation to take the element away. However, the notFull condition is putLock, but when the element is taken away, takeLock is used. Therefore, if the put method has putLock, The notFull condition is notified, so that other methods that add elements do not wait too long. Similarly, the notEmpty condition is also notified for the take method.
public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
Finally, let's talk about the remove and contains methods. Because you need to operate the entire linked list, you need to have a lock at the same time to operate.