继承体系
可以看到和ArrayBlockingQueue的继承体系并没有太大区别。相比较ArrayBlockingQueue,就类似于LinkedList对比ArrayList。ArrayBlockingQueued底层使用的是数组实现,而LinkedBlockingQueue则是使用链表进行实现。
重要属性
// 容量
private final int capacity;
// 元素数量,与ArrayBlockingQueue不同,使用的是原子类
private final AtomicInteger count = new AtomicInteger();
// 链表头
transient Node<E> head;
// 链表尾
private transient Node<E> last;
// 可以看到这个地方有两个ReentrantLock,分别是takeLock,putLock
// 这意味着入队列与出队列所使用的锁不一样,互相不会阻塞
private final ReentrantLock takeLock = new ReentrantLock();
// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
private final Condition notEmpty = takeLock.newCondition();
// 放锁
private final ReentrantLock putLock = new ReentrantLock();
// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
private final Condition notFull = putLock.newCondition();
构造方法
public LinkedBlockingQueue() {
// 如果没传容量,就使用最大int值初始化其容量
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
// 初始化head和last指针为空值节点
last = head = new Node<E>(null);
}
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
可以看到LinkedBlockingQueue默认使用Int的最大值,也就是默认是无界队列。可以主动传递容量值进行限定。除此之外也可以使用集合数据进行初始化,同ArrayBlokingQueue初始化类似,其内部加了锁,防止重排序或者可见性带来的线程安全问题
重要方法
入队方法
- add(E e) 调用offer(e)如果成功返回true,如果失败抛出异常
- offer(E e) 返回true/false
- put(E e) 不断循环调用入队方法,直到成功
- offer(E e, long timeout, TimeUnit unit) 不断循环调用入队方法,unit时间后放弃
// LinkedBlockingQueue 未覆盖此方法,使用AbstractQueue默认实现
public boolean add(E e) {
// 调用子类offer方法
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
// 达到容量直接返回false
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
// 锁住之后再次对比是否可以入队,不能入队则返回-1表达入队失败
if (count.get() < capacity) {
// 入队
enqueue(node);
// 大小加一,注意这里的getAndIncrement。比如count是1的话,c是等于1的
c = count.getAndIncrement();
// 判断是否没有达到容量,没有达到则唤醒
// 这里为啥要唤醒一下呢?
// 因为可能有很多线程阻塞在notFull这个条件上的
// 而取元素时只有取之前队列是满的才会唤醒notFull
// 为什么队列满的才唤醒notFull呢?
// 因为唤醒是需要加putLock的,这是为了减少锁的次数
// 所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
// c == 0意味着count原先是0,经过上面的操作之后队列内就存在元素了
if (c == 0)
signalNotEmpty();
return c >= 0;
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
// 计算到超出时间限定的时间长度
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
// 尝试加锁
putLock.lockInterruptibly();
try {
// 如果达到容量则一直循环
while (count.get() == capacity) {
// 如果已经超时则返回入队失败
if (nanos <= 0)
return false;
// 等待超时时长,等到条件符合的时候返回剩余时长,重新进入循环
nanos = notFull.awaitNanos(nanos);
}
// 入队
enqueue(new Node<E>(e));
c = count.getAndIncrement();
// 判定是否满了
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// c== 0 意味着入队成功,通知唤醒
if (c == 0)
signalNotEmpty();
return true;
}
public void put(E e) throws InterruptedException {
// 不允许null元素
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;
// 使用put锁加锁
putLock.lockInterruptibly();
try {
// 如果队列满了,就阻塞在notFull条件上
// 等待被其它线程唤醒
while (count.get() == capacity) {
notFull.await();
}
// 队列不满了,就入队
enqueue(node);
// 队列长度加1
c = count.getAndIncrement();
// 如果现队列长度如果小于容量
// 就再唤醒一个阻塞在notFull条件上的线程
if (c + 1 < capacity)
notFull.signal();
} finally {
// 释放锁
putLock.unlock();
}
// 如果原队列长度为0,现在加了一个元素后立即唤醒notEmpty条件
if (c == 0)
signalNotEmpty();
}
private void enqueue(Node<E> node) {
// 直接加到last后面
last = last.next = node;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
// 加take锁
takeLock.lock();
try {
// 唤醒notEmpty条件
notEmpty.signal();
} finally {
// 解锁
takeLock.unlock();
}
}
出队方法
出队也有4个方法:
- #poll() 方法:获取并移除此队列的头,如果此队列为空,则返回 null 。
- #poll(long timeout, TimeUnit unit) 方法:获取并移除此队列的头部,在指定的等待时间前等待可用的元素
- #take() 方法:获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
- #remove(Object o) 方法:从此队列中移除指定元素的单个实例(如果存在)。
public E remove() {
// 调用poll完成出队
E x = poll();
if (x != null)
return x;
else
// 如果没有数据则抛出异常
throw new NoSuchElementException();
}
public E poll() {
final AtomicInteger count = this.count;
// 队内无数据则返回null
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
// 执行出队
x = dequeue();
// 获得出队前的个数
c = count.getAndDecrement();
// 对内还存在数据,执行唤醒
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
// c是出队前的数据,这个时候已经取出一个,执行通知
if (c == capacity)
signalNotFull();
return x;
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
// 使用takeLock加锁
takeLock.lockInterruptibly();
try {
// 如果队列无元素,则阻塞在notEmpty条件上
while (count.get() == 0) {
notEmpty.await();
}
// 否则,出队
x = dequeue();
// 获取出队前队列的长度
c = count.getAndDecrement();
// 如果取之前队列长度大于1,则唤醒notEmpty
if (c > 1)
notEmpty.signal();
} finally {
// 释放锁
takeLock.unlock();
}
// 如果取之前队列长度等于容量
// 则唤醒notFull
if (c == capacity)
signalNotFull();
return x;
}
private E dequeue() {
// head节点本身是不存储任何元素的
// 这里把head删除,并把head下一个节点作为新的值
// 并把其值置空,返回原来的值
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
notFull.signal();
} finally {
putLock.unlock();
}
}
总结
LinkedBlockingQueue底层使用链表来维护队列,存在head与last指针指向链头与链尾。其内部存在两个锁分别控制出队与入队操作。