学习LinkedBlockingQueue需要掌握ReentrantLock 原理,或者了解其使用也行。
java 并发编程系列文章目录
在LinkedBlockingQueue注释上已经写明:
基于链表节点的可选有界阻塞队列。该队列对元素FIFO(先进先出)进行排序。队列的头是在队列中停留时间最长的元素。队列的尾部是在队列中停留时间最短的元素。新元素被插入到队列的尾部,队列检索操作获得队列头部的元素
使用两把锁那保证放入和获取时操作的线程安全性
//指定的容量 默认int最大值
private final int capacity;
//当前集合中的元素数量,AtomicInteger 保证原子性,不使用基本类型是因为这里有两把锁,不像ArrayBlockingQueue是一个ReentrantLock
private final AtomicInteger count = new AtomicInteger();
//链表的头结点 和 尾结点
transient Node<E> head;
private transient Node<E> last;
//获取元素的锁,保证从头部拿元素的线程安全
private final ReentrantLock takeLock = new ReentrantLock();
//获取元素为空的时候的条件等待锁
private final Condition notEmpty = takeLock.newCondition();
//放入元素的锁,保证从尾部添加元素的线程安全
private final ReentrantLock putLock = new ReentrantLock();
//添加元素为满的时候的条件等待锁
private final Condition notFull = putLock.newCondition();
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>(e);
final ReentrantLock putLock = this.putLock;
//上putLock
putLock.lock();
try {
//再次判断数量
if (count.get() < capacity) {
//放入链表中
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
//为啥这地方要唤醒添加元素阻塞的线程呢,因为可能当时这个线程添加的时候已经满了,然后阻塞了,此时另外一个线程去除几个元素,还没唤醒线程,同时这边已经放入一个线程,此时还是可以添加元素的,所以这样会更快的让阻塞的线程被唤醒进行添加元素
notFull.signal();
}
} finally {
putLock.unlock();
}
//这个C == 0的情况和notFull.signal()一样,具体原因就是takeLock和putLock是两把不同的锁,所以这属于优化
if (c == 0)
signalNotEmpty();
return c >= 0;
}
相对于offer方法 多了notFull.await(); 当元素满的时候会阻塞等待元素被取走,然后添加元素
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
//和offer区别在这
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
从链表中拿出一个元素
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//takeLock获取锁
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
//当没有数据获取的时候会阻塞在这等待元素的放入
notEmpty.await();
}
//从链表的头部获取一个元素,因为是尾部添加元素,头部获取元素
x = dequeue();
//c > 1的情况上面以描述,两把锁的原因
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
//同上
if (c == capacity)
signalNotFull();
return x;
}
相对于take方法,如果没有元素会返回null
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;
//获取take锁
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;
}
从链表中获取个元素,但是没有从链表中删除
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
//获取take锁
takeLock.lock();
try {
//查询到头部的元素返回
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
public boolean remove(Object o) {
if (o == null) return false;
//remove一个元素,此时需要对take 和 put两把锁都需要获取到才行,因为take不能拿到被删除的
//元素,put的时候如果删除的尾部节点元素呢,也会线程不安全
fullyLock();
try {
//遍历查找这个元素,找到就从链表中移除该元素
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
把所少个元素放入指定的集合中
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
boolean signalNotFull = false;
final ReentrantLock takeLock = this.takeLock;
//因为你要拿出一些元素放入到指定的集合中,所以是获取take锁
takeLock.lock();
try {
//取最小值,因为可能元素数量小于需要的元素数量
int n = Math.min(maxElements, count.get());
// count.get provides visibility to first n Nodes
Node<E> h = head;
int i = 0;
try {
//获取元素进行添加,并从链表中移除
while (i < n) {
Node<E> p = h.next;
c.add(p.item);
p.item = null;
h.next = h;
h = p;
++i;
}
return n;
} finally {
...
}
} finally {
takeLock.unlock();
if (signalNotFull)
signalNotFull();
}
}
LinkedBlockingQueue 是基于链表实现的阻塞队列。它相对于数组添加和删除的锁粒度变小,在增删方面线程同步阻塞的概率就会变小。同样也是用ReentrantLock来保证线程安全。置于和ArrayBlockingQueue谁的性能好,理论上来说LinkedBlockingQueue具有更高的吞吐,但是实际上还是看使用,这是不可预测的。