并发阻塞队列,LinkedBlockingQueue一端出一端入,并且结构改变线程安全的队列。其实队列从实现思想上比较容易理解,有以下特点:
1.链表结构(动态数组)
2.通过ReentrantLock实现锁
3.利用Condition实现队列的阻塞等待,唤醒
LinkedBlockingQueue
这是一个只能一端出一端如的单向队列结构,是有FIFO特性的,并且是通过两个ReentrantLock和两个Condition来实现的。先看它的结构基本字段:
/**
* 基于链表。
* FIFO
* 单向
*最大容量是Integer.MAX_VALUE.
*/
public class LinkedBlockingQueueAnalysis<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/*
* 两个方向。
* putLock
* takeLock
* 有些操作会需要同时获取两把锁。
* 例如remove操作,也需要获取两把锁
*/
//主要的node节点
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
//容量,一开始就固定了的。
private final int capacity;
//用AtomicInteger 来记录数量。
private final AtomicInteger count = new AtomicInteger();
//head节点 head.item == null
transient Node<E> head;
//last节点,last.next == null
private transient Node<E> last;
//take锁
private final ReentrantLock takeLock = new ReentrantLock();
//等待take的节点序列。
private final Condition notEmpty = takeLock.newCondition();
//put的lock。
private final ReentrantLock putLock = new ReentrantLock();
//等待puts的队列。
private final Condition notFull = putLock.newCondition();
...
}
LinkedBlockingQueue采用了两把锁来对队列进行操作,也就是队尾添加的时候,
队头仍然可以删除等操作。接下来看典型的操作。
put操作
首先看put操作:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException(); //e不能为null
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock; //获取put锁
final AtomicInteger count = this.count; //获取count
putLock.lockInterruptibly();
try {
while (count.get() == capacity) { //如果满了,那么就需要使用notFull阻塞
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity) //如果此时又有空间了,那么notFull唤醒
notFull.signal();
} finally {
putLock.unlock(); //释放锁
}
if (c == 0) //当c为0时候,也要根take锁说一下,并发下
signalNotEmpty(); //调用notEmpty
}
主要的思想还是比较容易理解的,现在看看enqueue 方法:
private void enqueue(Node<E> node) { //入对操作。
last = last.next = node; //队尾进
}
再看看signalNotEmpty方法:
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock(); //加锁
try {
notEmpty.signal(); //用于signal,notEmpty
} finally {
takeLock.unlock();
}
}
take操作
take操作,就是从队列里面弹出一个元素,下面看它的详细代码:
public E take() throws InterruptedException {
E x;
int c = -1; //设定一个记录变量
final AtomicInteger count = this.count; //获得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;
}
接下来看dequeue方法:
private E dequeue() {
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC 指向自己,帮助gc回收
head = first;
E x = first.item; //从队头出。
first.item = null; //将head.item设为null。
return x;
}
对于LinkedBlockingQueue来说,有两个ReentrantLock分别控制队头和队尾,这样就可以使得添加操作分开来做,一般的操作是获取一把锁就可以,但有些操作例如remove操作,则需要同时获取两把锁:
public boolean remove(Object o) {
if (o == null) return false;
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();
}
}
当然,除了上述的remove方法外,在Iterator的next方法,remove方法以及LBQSpliterator分割迭代器中也是需要加全锁进行操作的。
详细 LinkedBlockingQueue和LinkedBlockingDeque区别 可查看
原文:https://blog.csdn.net/anla_/article/details/79027867