LinkedBlockingQueue与ArrayBlockingQueue的区别
ArrayBlockQueue同步出队线程和入队线程用的都是同一把锁,而LinkedBlockingQueue出队有自己的锁,入队也有自己的锁。另一个区别就是数据结构的不同,即LinkedBlockingQueue用的是链表,而ArrayBlockingQueue用的是数组。
LinkedBlockingQueue的数据结构
private final int capacity; //容量
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger(); //队列中元素个数,这里为啥用原子引用类,因为入队线程和出队线程的锁不是同一把,他们都有自己的锁,所以是可以并发进行的,为了遍历计数的线程安全问题,故采用线程安全的原子类
/**
* Head of linked list.
* Invariant: head.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 */
private final ReentrantLock takeLock = new ReentrantLock(); //入队锁
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();//队列满了的条件队列
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock(); //出队锁
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();//出队空了的条件队列
构造方法
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE); //调用重载构造方法,初始化容量以及一个item为null的哨兵节点
final ReentrantLock putLock = this.putLock; //获取入队锁
putLock.lock(); // Never contended, but necessary for visibility 这里加锁不是为了避免互斥而是为了可见性
try {
int n = 0; //记录入队节点个数
for (E e : c) {
if (e == null) //节点不能为null
throw new NullPointerException();
if (n == capacity) //达到容量上限
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e)); //入队方法
++n; //入队节点个数加1
}
count.set(n); //设置节点个数
} finally {
putLock.unlock();
}
}
offer(响应中断超时的)
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) //原来节点个数加1, 即当前节点入队之后的队列中元素节点个数 如果还没有达到容量
notFull.signal(); //唤醒其他入队线程
} finally {
putLock.unlock();
}
if (c == 0) //说明入队之前队列节点个数为0, 有可能存在阻塞的出队线程所以去进行唤醒。
signalNotEmpty(); //唤醒出队线程
return true;
}
signalNotEmpty
private void signalNotEmpty() {//和synchronized一样需要在同步代码块里唤醒
final ReentrantLock takeLock = this.takeLock; //获取出队锁
takeLock.lock();
try {
notEmpty.signal(); //唤醒队列的一个阻塞出队线程
} finally {
takeLock.unlock();
}
}
poll (响应中断,超时唤醒)
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(); //获取原来节点的元素个数,计数记录的数量减少1
if (c > 1) //说明队列中还有节点可以进行出队
notEmpty.signal(); //唤醒
} finally {
takeLock.unlock();
}
if (c == capacity) //说明出队之前队列是满的,因此可能存在入队阻塞的线程,所以进行唤醒
signalNotFull(); //唤醒入队阻塞线程
return x;
}
signalNotFull
private void signalNotFull() {
final ReentrantLock putLock = this.putLock; //获取入队锁
putLock.lock();
try {
notFull.signal(); //唤醒
} finally {
putLock.unlock();
}
}
dequeue
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head; //记录头节点
Node<E> first = h.next; //头节点的后继
h.next = h; // help GC 原头节点断开,
head = first; //头节点指向新的节点
E x = first.item; //记录头节点的值
first.item = null; //头节点的值置为null
return x; //返回节点的值
}
头节点出队是被后面出队的节点给断开,这做法和AQS的同步队列一样
contains
public boolean contains(Object o) {
if (o == null) return false; //元素不能为null
fullyLock(); //而需要遍历整个队列,在整个期间为了线程安全,读写是不允许同时并发的,所以需要获取所有的写锁,有线程进行入队和出队
try {
for (Node<E> p = head.next; p != null; p = p.next) //进行遍历
if (o.equals(p.item))
return true;
return false;
} finally {
fullyUnlock();
}
}
获取全锁和释放全锁
void fullyLock() {
putLock.lock(); //入队锁
takeLock.lock();//出队锁
}
/**
* Unlocks to allow both puts and takes.
*/
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
remove 节点进行移除
public boolean remove(Object o) {
if (o == null) return false;
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();
}
}
unlink
void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null; //要断开的节点的值置为null
trail.next = p.next;//断开节点的前驱跨过断开的节点指向后继
if (last == p) //断开的节点是不是尾巴节点
last = trail; //尾指针重新指向
if (count.getAndDecrement() == capacity) //移除节点之前对垒满了,说明有入队线程在阻塞
notFull.signal(); //唤醒入队线程
}
总结
LinkedBlockingQueue入队和出队是可以并发进行的,采用了两把锁,两个同步队列,两个条件队列。