1.LinkedBlockingQueue是什么 ?
LinkedBlockingQueue 也是使用单向链表实现的,可以当做无界队列也可以当做有界队列来使用。可以通过构造函数指定界限,,LinkedBlockingQueue默认是有界限的,默认队列容量为Integer.MAX_VALUE,用户也可以自己指定容量,所以从一定程 度上可以说 LinkedBlockingQueue 是有界阻塞队列。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
2.LinkedBlockingQueue源码解析
// 队列容量
private final int capacity;
// 当前队列元素个数
private final AtomicInteger count = new AtomicInteger(0);
// 队头
private transient Node<E> head;
// 队尾
private transient Node<E> last;
// take, poll, peek 等读操作的方法需要获取到这个锁
private final ReentrantLock takeLock = new ReentrantLock();
// 当队列为空时,执行出队操作(比如 take )的线程会被放入这个条件队列进行等待
private final Condition notEmpty = takeLock.newCondition();
// put, offer 等写操作的方法需要获取到这个锁
private final ReentrantLock putLock = new ReentrantLock();
// 当队列满时 , 执行进队操作( 比如put)的线程会被放入这个条件队71]进行等待
private final Condition notFull = putLock.newCondition();
2.1 0ffer操作
向队列尾部插入一个元素,如果队列中有空闲则插入成功后返回 true,如果队列己满 则丢弃当前元素然后返回 false。 该方法是非阻塞 的 。
public boolean offer(E e) {
//空元素则抛出空指针异常
if (e == null) throw new NullPointerException();
//如采当前队列满则丢弃将要放入的元素, 然后返回 fals
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
//构造新节点,获取putLock独占锁
int c = -1;
Node<E> node = new Node<E>(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();
}
// 如果 c == 0,那么代表队列在这个元素入队前是空的(不包括head空节点),
// 那么所有的读线程都在等待 notEmpty 这个条件,等待唤醒,这里做一次唤醒操作
if (c == 0)
signalNotEmpty();
return c >= 0;
}
//入队列就是将 last 属性指向这个新元素,并且让原队尾的 next 指向这个元素
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
2.2put操作
向队列尾部插入一个元素,如果队列中有空 闲则插入后直接返回,如果队列己满则阻 塞当前线程,直到队列有空闲插入成功后返回。如果在阻塞时被其他线程设置了中断标志, 则 被阻塞线程会抛出 InterruptedException 异常而返回。另外,如果 e 元素为 null 则 抛出 NullPointerException 异 常。
public void put(E e) throws InterruptedException {
//如果为空元素则抛出空指针异常
if (e == null) throw new NullPointerException();
如果你纠结这里为什么是 -1,这就是个标识成功、失败的标志而已。
int c = -1;
//新节点,并获取独占锁
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
//如果队列满,等待 notFull 的条件满足。
while (count.get() == capacity) {
notFull.await();
}
// 入队
enqueue(node);
// count 原子加 1,c 还是加 1 前的值
c = count.getAndIncrement();
// 如果这个元素入队后,还有至少一个槽可以使用,调用 notFull.signal() 唤醒等待线程。
// 哪些线程会等待在 notFull 这个 Condition 上呢?
//则唤醒 notFull 的条件队列里面,因为调用了 notFull 的 await 操作(比如执行 put 方法而队列满了的时候)而被阻塞的一个 线程,因为队列现在有空闲所以这里可 以提前唤醒一个入队线程
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
// 如果 c == 0,那么代表队列在这个元素入队前是空的(不包括head空节点),
// 那么所有的读线程都在等待 notEmpty 这个条件,等待唤醒,这里做一次唤醒操作
signalNotEmpty();
}
// 元素入队后,如果需要,调用这个方法唤醒读线程来读
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
2.3take操作
获取当前队列头部元素并从队列里面移除它。 如果队列为空则阻塞当前线程直到队列不为空然 后返回元 素 ,如 果在 阻 塞 时 被其 他 线程设置 了中 断 标 志 , InterruptedException异常而返回
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
// 获取锁
takeLock.lockInterruptibly();
try {
//当前队列为空则阻塞拉起 ,等待 notEmpty 这个条件满足再继续执行
while (count.get() == 0) {
notEmpty.await();
}
// 出队并递减计数
x = dequeue();
// count 进行原子减 1
c = count.getAndDecrement();
// 如果这次出队后,队列中至少还有一个元素,那么调用 notEmpty.signal() 唤醒其他的读线程
if (c > 1)
notEmpty.signal();
} finally {
// 出队后释放掉 takeLock
takeLock.unlock();
}
// 如果 c == capacity,那么说明在这个 take 方法发生的时候,队列是满的
// 既然出队了一个,那么意味着队列不满了,唤醒写线程去写
if (c == capacity)
signalNotFull();
return x;
}
// 取队头,出队
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
// 之前说了,头结点是空的
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
// 设置这个为新的头结点
head = first;
E x = first.item;
first.item = null;
return x;
}
// 元素出队后,如果需要,调用这个方法唤醒写线程来写
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
2.4remove 操作
public boolean remove(Object o) {
if (o == null) return false;
//双重加锁
fullyLock();
try {
//遍历 找到则删除并返回true
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;
}
}
//找不到则返回 false
return false;
} finally {
//释放双重锁 。
fullyUnlock();
}
}
//寻找要删除的元素,找不到则直接返回 false,找到则执行 unlink 操作 。 unlik 操作的代码如下。
void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
如果当前队列满 ,则删除后,也不忘记唤醒等待的线程
if (count.getAndDecrement() == capacity)
notFull.signal();
}
3.总结
LinkedBlockingQueue 的内部是通过单向链表实现的,使用头、尾节点来进行入队和出队操作,也就是入队操作都是对尾节点进行操作,出队操作都是对头节点进行操作 。对头、尾节点的操作分别使用了单独的独占锁从而保证了原子性,所以出队和入队操作是可以同时进行的 。 另外对头 、 尾节点的独占锁都配备了一个条件队 列,用来存放被阻塞的线程,并结合入队、出队操作实现了 一个生产消费模型 。