文章目录
1.节点:Node
item保存当前节点的正式的属性值,所以他是一个泛型标记的
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; }
}
2.构造函数
默认构造函数的容量是Integer.MAX_VALUE
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);
}
3.重要参数说明
1.队列容量:capacity
最大容量
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;
2.队列当前数量:count
当前的容量
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();
3.控制 take,poll的锁:takeLock
/** Lock held by take, poll, etc */
//控制 take,poll的锁
private final ReentrantLock takeLock = new ReentrantLock();
4.控制put ,offer方法的锁:putLock
/** Lock held by put, offer, etc */
//控制put ,offer方法的锁
private final ReentrantLock putLock = new ReentrantLock();
5.线程队列(等待移除出队列的操作):notEmpty
注意这个Condition是从lock中来的,lock锁定的是当前的队列的操作,而这边的notEmpty表示的就是当前的某一种队列操作的线程:
比如说这边根据上面的takeLock可以知道,他其实对应的是take和poll操作的线程
/** Wait queue for waiting takes */
// 等待被获取(take)的队列
private final Condition notEmpty = takeLock.newCondition();
6.线程队列(等待添加到队列的操作):notFull
注意这个Condition是从lock中来的,lock锁定的是当前的队列的操作,而这边的notFull表示的就是当前的某一种队列操作的线程:
比如说这边根据上面的putLock可以知道,他其实对应的是put 和offer操作的线程
/** Wait queue for waiting puts */
//等待被添加(put)的队列
private final Condition notFull = putLock.newCondition();
4.出队列:poll
这边需要注意的是:
getAndDecrement这个方法返回的是修改前的值,这个没有理解搞得我有点懵逼
然后这边会唤醒下一个需要 take、poll 操作的线程
并且在最后判断修改前的队列数量是否等于最大容量(capital),判断是否唤醒阻塞中的put、offer的操作线程:notFull,因为这边是加锁的,所以不需要当前线程的并发可能导致的信息丢失问题。
public E poll() {
final AtomicInteger count = this.count;
//如果当前的节点数量为0,直接返回null
if (count.get() == 0) {
return null;
}
E x = null;
int c = -1;
//拿到take锁
final ReentrantLock takeLock = this.takeLock;
//锁定
takeLock.lock();
try {
if (count.get() > 0) {
//拿到第一个节点对应的元素值
x = dequeue();
//总数 - 1 (这边是lock了,所以这边cas一定会成功)
c = count.getAndDecrement();
//如果当前总数大于1
if (c > 1) {
//唤醒下一个需要移除队列节点操作(take、poll)的线程.
notEmpty.signal();
}
}
} finally {
//最终解锁
takeLock.unlock();
}
//the previous value,因为上面的getAndDecrement返回的是之前的值,所以如果上一次的数量等于容量,那么减一之后就需要唤醒需要添加到队列(put、offer)操作的线程.
if (c == capacity) {
signalNotFull();
}
//返回元素
return x;
}
1.移除头结点:dequeue
这边有一个移除头节点的操作
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
//拿到第一个节点
Node<E> h = head;
Node<E> first = h.next;
// help GC
h.next = h;
//第一个节点出队列
head = first;
E x = first.item;
first.item = null;
//返回节点包含的元素
return x;
}
2.唤醒下一个节点:signal
唤醒下一个节点的操作其实相当于是把在等待的条件队列中的节点添加到同步队列.
public final void signal() {
//如果线程不等于当前持锁线程,报错
if (!isHeldExclusively()) {
throw new IllegalMonitorStateException();
}
Node first = firstWaiter;
//如果第一个节点不为空,从第一个节点开始进行唤醒操作
if (first != null) {
doSignal(first);
}
}
private void doSignal(Node first) {
do {
//没有节点的情况
if ((firstWaiter = first.nextWaiter) == null){
lastWaiter = null;
}
first.nextWaiter = null;
//循环退出的条件:1.节点加入到同步队列,2.没有对应节点
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
5.添加节点:put
put这边会根据当前容量和最大容量的比较,决定操作线程是挂起等待还是正常执行,这边通过加putLock保证高并发不互相干扰
public void put(E e) throws InterruptedException {
//节点如果为null报错
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 {
//如果count数量等于队列容量,说明操作队列已经满了,只能添加到阻塞队列中等待,在await中的操作会被park,等待唤醒
while (count.get() == capacity) {
//添加到条件队列等待
notFull.await();
}
//如果总数不等于容量往队列中添加节点
enqueue(node);
//增加当前线程数量
c = count.getAndIncrement();
//节点数量 < 容量 - 1,也就是说下一次入队还不会达到队列的最大容量
if (c + 1 < capacity) {
//从条件队列中唤醒,添加到同步队列
notFull.signal();
}
} finally {
//最终解锁
putLock.unlock();
}
//如果当前数量小于0,唤醒下一个节点不为空的节点
if (c == 0) {
signalNotEmpty();
}
}
6.删除节点:remove
删除操作会把putLock和takeLock都拿到,在删除的时候不允许其他 操作线程执行他们的操作
public boolean remove(Object o) {
//如果节点为空,则移除失败
if (o == null) {
return false;
}
//要把put和take都锁住
fullyLock();
try {
//遍历每一个节点,如果节点的值和当前要删除的值对应的上,则取消关联(删除)
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;
}
}
return false;
} finally {
//最终两个都解锁
fullyUnlock();
}
}
void unlink(Node<E> p, Node<E> trail) {
p.item = null;
trail.next = p.next;
if (last == p) {
last = trail;
}
//如果前一次操作的容量和最大容量一样,说明删除一个操作线程之后,可以把要下一个 添加操作的线程唤醒添加到同步队列
if (count.getAndDecrement() == capacity) {
notFull.signal();
}
}
7.拿到队头节点但不出队:peek
这边可以看到,因为peek没有改变队列,所以不会操作notEmpty、notFull两个队列
public E peek() {
if (count.get() == 0){
//如果总数为0,则返回null
return null;
}
//锁住take锁
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
8.添加节点,容量满直接废弃:offer
这个方法和put类似,但是如果当前队列已经达到最大容量,则会直接废弃,返回false,这个方法有一个重载方法会记录时间,当时间到达的时候如果对队列还是达到最大容量才进行放弃;
public boolean offer(E e) {
if (e == null) {
throw new NullPointerException();
}
final AtomicInteger count = this.count;
//这边如果当前的数量大于最大容量,则直接返回false。否则后面的操作和put类似
if (count.get() == capacity){
return false;
}
//下面的操作和put类似
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();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
9.总结
这边的两个锁对应两个部分的操作,拿出操作和放入操作,并且维护两个ConditionObject对象,表示挂起等待的拿出操作的线程队列和挂起等待的放入操作的线程队列。
那么就很好理解了,如果当前的数量count = 0,可以放入(如果有的话要唤醒),并且不能拿。
如果当前数量为最大容量-1,则要把挂起等待的放入操作的线程唤醒,往同步队列中添加放入操作线程