LinkedBlockingQueue简介
LinkedBlockingQueue是LinkedList对应的并发版本,是基于链表的。
通过名字中带有Blocking就可以看出当入队和出队不满足条件的时候,会阻塞
如果想彻底了解LinkedBlockingQueue的话,需要了解ReentrantLock的相关知识,如果对ReentrantLock不太了解的话,建议看一下我之前写的关于ReentrantLock的文章ReentrantLock源码详解
LinkedBlockingQueue源码解析
主要变量
//队列中元素个数
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 LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//head为头节点,last为尾节点
//初始化为null,可以看出当queue为空的时候,head==tail
last = head = new Node<E>(null);
}
//默认的构造方法
public LinkedBlockingQueue() {
//队列大小为Integer.MAX_VALUE
this(Integer.MAX_VALUE);
}
put()
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
//利用ReentrantLock独占锁来加锁,保证同时只有一个线程来put
final ReentrantLock putLock = this.putLock;
//利用AtomicInteger来表示queue中的元素个数
final AtomicInteger count = this.count;
//可打断的加锁
putLock.lockInterruptibly();
try {
// private final Condition notFull = putLock.newCondition();
//如果队列满了,就调用notFull。await()。notFull是putLock的条件变量,当调用notFull.await()会将putLock释放,阻塞在等待队列notFull上
while (count.get() == capacity) {
notFull.await();
}
//入队,不用获得takeLock,因为与出队操作不涉及共享变量
//从入队代码可以看出head是一个哨兵节点,不存放任何实际数据
//last = last.next = node;
enqueue(node);
//count++
c = count.getAndIncrement();
//如果队列未满,唤醒被阻塞的入队线程
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
//如果c == 0,说明入队之前队列为空,唤醒出队的等待线程
if (c == 0)
signalNotEmpty();
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
//获取出队锁
takeLock.lock();
try {
//唤醒出队等待线程
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
offer()
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.lock();
try {
//和put()不同的是,当队列满的时候,offer()直接返回false,不会阻塞
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
take()
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
//如果队列为空,放弃takeLock,阻塞在等待队列notEmpty上
while (count.get() == 0) {
notEmpty.await();
}
//出队
x = dequeue();
//count--;
c = count.getAndDecrement();
//如果队列不为空,唤醒出队等待线程
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
//如果队列不为空,唤醒入队等待线程
if (c == capacity)
signalNotFull();
return x;
}
private E dequeue() {
//head是哨兵节点,不存放数据,实际的头节点是head.next
Node<E> h = head;
//head的next
Node<E> first = h.next;
h.next = h;
//将head踢出
head = first;
//first的item才是第一个元素,head是哨兵节点
E x = first.item;
first.item = null;
//从dequeue方法可以看出,queue中始终有一个哨兵head节点,不存储任何数据,queue中第一个元素是head.next
return x;
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
//唤醒入队等待线程
notFull.signal();
} finally {
putLock.unlock();
}
}
poll()
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;
takeLock.lock();
try {
//和take()方法不同的是,如果队列为空,就直接返回null,不会阻塞
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
poll()和take()方法不同的是,如果队列为空,就直接返回null,不会阻塞。
peek()
peek()是获取队列中头节点的值,但是不出队
public E peek() {
//如果队列为空,返回null
if (count.get() == 0)
return null;
//获取出队锁,防止在peek()期间由其他线程执行出队操作
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
//head是哨兵节点,head.next才是第一个节点
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
方法总结
入队
方法名 | 做法 |
---|---|
put() | 如果队列满了,就阻塞,当队列不满的时候,会再执行入队操作 |
offer() | 如果队列满了,返回false |
出队
方法名 | 做法 |
---|---|
take() | 如果队列为空,就阻塞,当队列不空的时候,会再执行出队操作 |
poll() | 如果队列空了,返回null |
peek() | 返回队列首元素,不会出队 |