LinkedBlockingQueue简介
LinkedBlockingQueue是一个基于单向链表实现的线程安全的阻塞队列,按照FIFO(先进先出)的原则,在队列尾部插入新数据,从队列头部取出数据,同时为了防止队列无限膨胀,LinkedBlockingQueue还支持设置容量,默认容量为Integer.MAX_VALUE
LinkedBlockingQueue特性
- 继承AbstractQueue,是一个FIFO的队列
- 线程安全,多线程竞争时会阻塞等待,通过ReentrantLock实现
- 通过单向链表实现,并且支持设置链表容量
- 分为读锁和写锁,读取和写入不互斥
LinkedBlockingQueue的数据结构
// 链表头节点
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();
Node类定义
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
LinkedBlockingQueue部分源码解析
构造方法
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
构造方法初始化容量,同时创建一个空节点,此时头节点和尾节点为同一节点
变更方法
- put方法
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>(e);
// 获取写入数据锁
final ReentrantLock putLock = this.putLock;
// 获取当前已有的元素数
final AtomicInteger count = this.count;
// 加锁并且当前线程中断时抛出中断异常
putLock.lockInterruptibly();
try {
// 当达到最大容量时无限等待队列空闲空间
while (count.get() == capacity) {
notFull.await();
}
// 向队列中增加元素
enqueue(node);
// 设置队列容量+1
c = count.getAndIncrement();
// 如果未超过最大容量则唤醒写入等待的线程
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// c==0表示队列中有1条数据,唤醒所有等待取数据的线程
if (c == 0)
signalNotEmpty();
}
- enqueue方法,向链表中添加元素的方法
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
这里的写法,实际相当于:
last.next = node;
last = node;
- take方法
public E take() throws InterruptedException {
E x;
int c = -1;
// 获取当前队列中元素总数
final AtomicInteger count = this.count;
// 获取当前队列读取数据的锁
final ReentrantLock takeLock = this.takeLock;
// 加锁并且当前线程中断时抛出中断异常
takeLock.lockInterruptibly();
try {
// 当前队列元素总数为0时无限等待
while (count.get() == 0) {
notEmpty.await();
}
// 获取队列头部的数据
x = dequeue();
// 当前队列元素总数减一,注意c为减一之前的值
c = count.getAndDecrement();
// 取出元素后如果当前队列的元素总数大于0则唤醒所有等待消费的线程
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
// 如果取出元素前的队列元素总数等于队列的容量,则唤醒所有等待写入的线程,因为此时已经取出一个元素有空余的空间可以进行写入
if (c == capacity)
signalNotFull();
return x;
}
- dequeue方法
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
// 获取队列底层链表的头节点,链表头节点为空
Node<E> h = head;
// 获取队列的第一个元素,因为链表头节点为空,因此链表的第一个有效节点为h.next
Node<E> first = h.next;
// 删除原始头节点的引用
h.next = h; // help GC
// 设置head节点为获取到的第一个有效节点
head = first;
// 获取第一个有效节点存储的元素
E x = first.item;
// 将头节点的元素设置为null
first.item = null;
return x;
}
注意:因为在LinkedBlockingQueue初始化的时候就会创建一个链表的表头和表尾节点,是一个空节点,因此在实际向LinkedBlockingQueue添加数据时是从链表的第二个元素开始存储数据的,所以取数据也是从表头节点的下一节点取数据
- signalNotFull方法
private void signalNotFull() {
// 获取写入数据锁
final ReentrantLock putLock = this.putLock;
// 加锁
putLock.lock();
try {
// 唤醒写入等待的线程
notFull.signal();
} finally {
putLock.unlock();
}
}
遍历方法
- iterator方法
public Iterator<E> iterator() {
return new Itr();
}
- Itr类实现
private class Itr implements Iterator<E> {
// 当前节点
private Node<E> current;
// 上一次返回节点
private Node<E> lastRet;
// 当前元素
private E currentElement;
Itr() {
// 同事对队列的读取和写入同时加锁
fullyLock();
try {
// 当前节点为链表的第一个有效节点,即head的下一个节点
current = head.next;
if (current != null)
// 设置当前元素
currentElement = current.item;
} finally {
fullyUnlock();
}
}
public boolean hasNext() {
return current != null;
}
// 获取需要遍历的下一节点
private Node<E> nextNode(Node<E> p) {
for (;;) {
Node<E> s = p.next;
// 如果s==p则表示该节点对应的元素已被取出,那么直接取当前链表的第一个有效元素
if (s == p)
return head.next;
// s==null表示遍历结束,s.item不为null则返回当前节点
if (s == null || s.item != null)
return s;
p = s;
}
}
// 返回当前元素值
public E next() {
// 对当前队列增加读写锁
fullyLock();
try {
if (current == null)
throw new NoSuchElementException();
// 获取当前节点元素
E x = currentElement;
// 将当前节点赋值给上次获取节点
lastRet = current;
// 获取下一个需要遍历的节点
current = nextNode(current);
// 获取下一个需要遍历的节点对应的元素
currentElement = (current == null) ? null : current.item;
return x;
} finally {
fullyUnlock();
}
}
// 删除元素
public void remove() {
if (lastRet == null)
throw new IllegalStateException();
// 给队列的读取和写入加锁
fullyLock();
try {
// 获取遍历的前一个节点
Node<E> node = lastRet;
lastRet = null;
// 从头节点遍历整个链表,删除指定的节点
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (p == node) {
unlink(p, trail);
break;
}
}
} finally {
fullyUnlock();
}
}
}
- unlink方法
void unlink(Node<E> p, Node<E> trail) {
// 置空p节点对应的元素
p.item = null;
// 前置节点的next设置为当前待删除节点的next
trail.next = p.next;
// 如果p是尾节点,则将trail设置为尾节点
if (last == p)
last = trail;
// 如果删除元素前队列容量已满,则唤醒所有需要写入的线程
if (count.getAndDecrement() == capacity)
notFull.signal();
}