横向比较
LinkedBlockingQueue对应的是LinkedLis和ConcurrentLinkedQueue,LinkedList是一个线程不安全的双向链表结构的队列,ConcurrentLinkedQueue是一个线程安全的单链表结构的队列,ConcurrentLinkedQueue解决了LinkedList线程不安全的问题,而且是通过写时next使用cas和volatile来实现,是一个无锁化操作,极大保证了读写的性能,但由此带来的缺点是读可能数据不一致,而且ConcurrentLinkedQueue是一个无界队列,操作不当的话可能会导致内存溢出。LinkedBlockingQueue主要是解决了ConcurrentLinkedQueue的两个问题:队列的无界和读数据不一致问题,但肯定在性能上就比ConcurrentLinkedQueue差一些。
put("张三")方法
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
拿到putLock锁,队列现在元素数量count,然后上锁
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
当队列现在元素数量等于最大容量时,使用putLock锁的condition放入await的等待队列中
while (count.get() == capacity) {
notFull.await();
}
直接把新的node挂到last.next上并成为新的last的节点
enqueue(node);
元素数量+1
c = count.getAndIncrement();
元素数量+1后小于最大容量时,使用putLock锁的condition唤醒await等待队列,
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
当放入当前元素之前是空的,说明之前队列中没有元素,此时可能take操作有线程在takeLock的condition的await等待队列中,所以需要尝试signal takeLock锁的condition中await等待队列的线程
if (c == 0)
signalNotEmpty();
}
总结
-
在put元素时,首先拿到的putLock重入锁,而且记录元素个数的count也是AtomicInteger,保证了count累加的线程安全性
-
LinkedBlockingQueue的node节点、head、tail,next等都没有使用volatile修饰,而且挂新节点也没有ConcurrentLinkedQueue那么复杂的自旋和判断,所以这就是锁带来的一个好处(或者说是锁粒度带来的好处,粒度越小可能存在的线程安全问题越多),代码复杂度会大大降低,各种多线程下写失败的情况就不会出现,
-
当队列现在元素数量等于最大容量时,使用putLock锁的condition放入await的等待队列中,并会释放putLock,这样就实现了达到容量就阻塞的效果
-
元素数量+1后小于最大容量时,此时说明该线程的元素放进队列后也不会达到最大容量,所以使用putLock锁的condition尝试唤醒await等待队列,这么判断的好处是减少signal无效执行的次数和多一个能唤醒putLock锁的condition的awart等待队列的方式,因为大于等于最大容量执行signal方法后,await等待队列中的线程只是挪到了AQS的等待队列,该线程即使获取到锁又会立即进入putLock锁的condition的awart等待队列,因为此时元素数量已经大于最大容量了
-
当放入当前元素之前是空的,说明之前队列中没有元素,此时可能take操作有线程在takeLock的condition的await等待队列中,所以需要尝试signal takeLock锁的condition中await等待队列的线程
-
put和take实际上使用的是两把锁,写和读是互不干扰,这跟ReentrantReadWriteLock就不一样了,ReentrantReadWriteLock是写跟写互斥、写跟读互斥、读跟读不互斥。而LinkedBlockingQueue是写跟写互斥、读跟读互斥、写跟读不互斥。
take()方法
-
跟put方法差不多,put方法是在tail挂元素,take是在head出元素,put方法使用了putLock,take方法使用了takeLock
-
put方法在元素达到最大容量时会使用putLock锁的condition放入await的等待队列,take方法在队列为空时会使用takeLock锁的condition放入await的等待队列
-
put方法在元素数量+1后小于最大容量会尝试唤醒putLock锁的condition放入await的等待线程,take方法在元素数量大于1时会尝试唤醒takeLock锁的condition放入await的等待线程
-
put方法在放入当前元素之前是空的时会尝试signal takeLock锁的condition中await等待队列的线程,take方法在拿出当初当前元素之前时满的时会尝试signal putLock锁的condition中await等待队列的线程