LinkedBlockingQueue
LinkedBlockingQueue是一个基于链表实现的阻塞队列
,该阻塞队列的大小默认为Integer.MAX_VALUE
,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限(随着元素的添加,队列的大小会动态增加,如果剩余内存不足,会出现OOM)。为了避免队列过大造成机器负载或者内存爆满的情况出现,在使用的时候建议手动传一个队列的大小
。
使用
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueExample {
private static final int QUEUE_CAPACITY = 5;
private static final int PRODUCER_DELAY_MS = 1000;
private static final int CONSUMER_DELAY_MS = 2000;
public static void main(String[] args) throws InterruptedException {
// 创建一个容量为QUEUE_CAPACITY的阻塞队列
BlockingQueue<String> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
// 创建一个无界的阻塞队列
BlockingQueue<String> queueMax = new LinkedBlockingQueue<>();
// 创建一个生产者线程
new Thread(() -> {
while (true) {
try {
// 在队列满时阻塞
queue.put("producer");
System.out.println("生产了一个元素,队列中元素个数:" + queue.size());
Thread.sleep(PRODUCER_DELAY_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 创建一个消费者线程
new Thread(() -> {
while (true) {
try {
// 在队列为空时阻塞
String element = queue.take();
System.out.println("消费了一个元素,队列中元素个数:" + queue.size());
Thread.sleep(CONSUMER_DELAY_MS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
原理
LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。
链表结构
/**
* Linked list node class
*/
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; }
}
构造方法
可以看到,当不知道队列大小时,则默认采用Integer.MAX_VALUE作为队列的大小。
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
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;
// 初始化head和last指针为空值节点
last = head = new Node<E>(null);
}
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}, initially containing the elements of the
* given collection,
* added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are 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();
}
}
内部常量
/** The capacity bound, or Integer.MAX_VALUE if none */
// 队列容量
private final int capacity;
/** Current number of elements */
// 元素数量
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.
* Invariant: head.item == null
*/
// 链表头 本身是不存储任何元素的,初始化时item指向null
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next == null
*/
// 链表尾
private transient Node<E> last;
/** Lock held by take, poll, etc */
// take锁 锁分离,提高效率
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
// notEmpty条件
// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
// put锁
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
// notFull条件
// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
private final Condition notFull = putLock.newCondition();
入队put方法
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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;
// 使用put锁进行加锁
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
// 如果当前队列数量和队列最大值相等,则进行notFull等待(阻塞生产者线程)
while (count.get() == capacity) {
notFull.await();
}
// 队列不满时,入队
enqueue(node);
// 获取当前队列长度,同时将当前队列长度+1
c = count.getAndIncrement();
// 如果现队列长度小于容量,notFull条件队列转同步队列,准备唤醒一个阻塞在notFull条件上的线程(可以继续入队)
// 这里为啥要唤醒一下呢?
// 因为可能有很多线程阻塞在notFull这个条件上,而取元素时只有取之前队列是满的才会唤醒notFull,此处不用等到取元素时才唤醒
if (c + 1 < capacity)
notFull.signal();
} finally {
// put锁解锁,唤醒其余生产者线程入队
putLock.unlock();
}
// 如果原队列长度为0,;立即唤醒阻塞在notEmpty上的线程
if (c == 0)
signalNotEmpty();
}
/**
* Links node at end of queue.
*
* @param node the node
*/
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
// 直接加到last后面,last指向入队元素
last = last.next = node;
}
/**
* Signals a waiting take. Called only from put/offer (which do not
* otherwise ordinarily lock takeLock.)
*/
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
// takeLock加锁
takeLock.lock();
try {
// notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程
notEmpty.signal();
} finally {
// takeLock解锁,唤醒消费者线程
takeLock.unlock();
}
}
出队take方法
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();
// 获取原队列长度,同时原队列长度-1
c = count.getAndDecrement();
if (c > 1)
// 如果原队列长度大于1,则唤醒其余消费者线程
notEmpty.signal();
} finally {
// takeLock解锁
takeLock.unlock();
}
// 为什么队列是满的才唤醒阻塞在notFull上的线程呢?
// 因为唤醒是需要加putLock的,这是为了减少锁的次数,所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程,
// 这也是锁分离带来的代价
// 如果取之前队列长度等于容量(已满),则唤醒阻塞在notFull的线程
if (c == capacity)
signalNotFull();
return x;
}
/**
* Removes a node from head of queue.
*
* @return the node
*/
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
// 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;
}
/**
* Signals a waiting put. Called only from take/poll.
*/
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
// notFull条件队列转同步队列,准备唤醒阻塞在notFull上的线程
notFull.signal();
} finally {
// putLock解锁,真正的唤醒生产者线程
putLock.unlock();
}
}