阻塞队列LinkedBlockingQueue原理和源码解析

1 简介

LinkedBlockingQueue在并发编程中使用较多,最常见的应用场景是生产者-消费者模式中。
下面我们详细分析下实现原理,达到能手写的目的。
LinkedBlockingQueue实现了BlockingQueue接口,关于BlockingQueue几个核心方法的区别,请参考另外一篇文章《BlockingQueue(LinkedBlockingQueue/ArrayBlockingQueue)核心方法比较说明》
另外,阻塞队列的实现,还使用了Condition接口的方法,具体可参考《Java Condition接口使用Demo&原理分析》

2 类的构成

解释一些主要的内部类和属性值。

//链表节点
static class Node<E> {
    //节点元素值
    E item;
    //指向下一个节点的指针
    Node<E> next;
	//构造方法
    Node(E x) { item = x; }
}
//队列的容量,一旦指定,不允许修改
private final int capacity;
//队列中节点的个数
//使用原子整形可以保证并发场景的准确性;因为插入元素和取出元素都会改变count值,存在并发
private final AtomicInteger count = new AtomicInteger();
//链表的头节点,内部元素值是null
transient Node<E> head;
//链表尾部节点,其后边不再有节点
private transient Node<E> last;
//插入元素时使用的锁
private final ReentrantLock takeLock = new ReentrantLock();
//Condition内部维护了一个等待队列,notEmpty用于存储等待 从链表中取出元素的线程节点
private final Condition notEmpty = takeLock.newCondition();
//从队列中取出元素时使用的锁
private final ReentrantLock putLock = new ReentrantLock();
//notFull用于存放等待 往链表中插入元素的线程节点
private final Condition notFull = putLock.newCondition();

3 核心方法

3.1加入方法

3.1.1 add(E e)

此方法在其父类AbstractQueue中;
作用就是往队列中加入元素,成功立即返回true,失败立即抛出异常。

public boolean add(E e) {
  if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

关键方法是offer(e),我们继续往下看。

3.1.2 offer(E e)

此方法非阻塞的向队列插入元素,成功插入队列,返回true;插入失败,比如队列满了,返回false。

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 {
        if (count.get() < capacity) {
            //加入队列中,直接拼接到队列尾部
            enqueue(node);
            //获取加一之前的值
            c = count.getAndIncrement();
            //元素个数小于容量,说明队列不满,发出不满通知
            //注意c是加一之前的值,所以要c + 1后才是真实的队列元素个数
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    //c的值是此方法插入之前的值,如果是0,说明插入之前队列是空的,那么插入之后,肯定就不是空了,
    //此时要发出非空通知,告诉等着取元素的线程,可以从队列取元素了
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

3.1.3 offer(E e, long timeout, TimeUnit unit)

此方法向队列插入元素,成功插入队列,返回true;如果队列是满的,则等待一定时间;如果时间到了队列依然是满的,返回false。

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();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}

此方法比offer(E e)就多了个notFull.awaitNanos(nanos)方法,打开看下:

//线程等待一定时间
//返回值理论上应该等于0,但是因为此方法中等待是以1000纳秒为单位,可能剩余点零头
public final long awaitNanos(long nanosTimeout)
       throws InterruptedException {
    //如果队列被中断,抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //加入condition等待队列,并返回此节点
    Node node = addConditionWaiter();
    //从AQS队列中移除,因为已经加入到了condition等待队列
    int savedState = fullyRelease(node);
    //停止等待的时刻
    final long deadline = System.nanoTime() + nanosTimeout;
    int interruptMode = 0;
    //isOnSyncQueue(node):检查node节点是否在AQS的队列中
    //如果不在,说明还没有被唤醒,没有资格竞争锁,进入沉睡状态
    while (!isOnSyncQueue(node)) {
        //如果还有剩余等待时间,则继续等待;
        if (nanosTimeout <= 0L) {
        	//如果没有剩余等待时间,则取消等待,并将节点转移到AQS队列中
            transferAfterCancelledWait(node);
            break;
        }
        if (nanosTimeout >= spinForTimeoutThreshold)
            //如果剩余时间大于1000纳秒,线程阻塞1000纳秒
            LockSupport.parkNanos(this, nanosTimeout);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        nanosTimeout = deadline - System.nanoTime();
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
    //如果本节点后边有排队的,遍历他们的状态,如果等待状态被取消,则从队列中删除
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
        //返回剩余等待时间
    return deadline - System.nanoTime();
}

3.1.4 put(E e)

此方法,以阻塞方式向队列中插入元素,如果队列没满,则成功插入;如果队列满了,则插入线程阻塞,等待队列非满的通知。

public void put(E e) throws InterruptedException {
    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;
    //lockInterruptibly()与lock()的区别是:前者可以在等锁的过程中,响应别的线程给的中断信号,然后停止休眠状态
    putLock.lockInterruptibly();
    try {
        //如果队列满了,则等待
        //此处用while,不会重复await,因为执行一旦执行await,线程就休眠了;while是为了保证休眠后,如果被其他线程唤醒,
        //需要及时再次判断队列是否已满,满了需要再次进入await,进而放置超出队列容量
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

可以看到,此方法中,使用 notFull.await();进行阻塞等待,这个方法是AQS类中的内部类ConditionObject中的方法:

public final void await() throws InterruptedException {
   if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        //线程阻塞
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

await()方法的作用就是初始化一个节点,并加入condition的等待队列中;
注意,虽然put()方法中是while循环调await方法的,但是并不会导致重复插入等待队列。因为正常的话,while第一次循环中,就在await方法中阻塞住了,不会循环第二次。

3.2 拿出方法

3.2.1 remove(Object)

从队列中移除某个具体的元素。成功返回true,如果此元素不存在,返回false。

public boolean remove(Object o) {
    if (o == null) return false;
    //同时获取put锁和take锁,就是remove操作的时候,不允许存取操作
    // putLock.lock();
    // takeLock.lock();
    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();
    }
}

3.2.2 poll()

从队列头部取出一个元素立即返回,如果队列是空的,则立即返回null

public E poll() {
    final AtomicInteger count = this.count;
    //count是0,说明队列是空的,直接返回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();
            //队列基数减1
            c = count.getAndDecrement();
            if (c > 1)
            	//如果队列不是空的,通知别人
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

这个方法的作用就是删除队列的第一个元素,并返回此元素
有个疑问:为啥要假设head是空的,直接返回了第二个元素

 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;
    return x;
}

3.2.3 poll(long , TimeUnit)

取出队列头部元素并返回;如果队列是空的,则等待一定时间;如果时间到了依然没有获取到值,返回null。

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;
}

其中,关键部分就是比epoll()多了个等待时间方法: notEmpty.awaitNanos(nanos);

3.2.4 take()

从队列头部取出元素,并返回;如果阻塞队列为空,将阻塞等待。

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            //队列是空的,则加入notEmpty condition的等待队列
            notEmpty.await();
        }
        //从队列中删除此元素
        x = dequeue();
        //阻塞队列计数减一,并返回减一之前的数
        c = count.getAndDecrement();
        if (c > 1)
        //如果队列不是空,通知因为取元素而等待的消费者
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    //取出动作发生后,必须判断队列是否不满
    //这里c是减一之前的值,此时等于队列容量,那么减一之后,肯定是不满的,发出非满通知
    if (c == capacity)
        signalNotFull();
    return x;
}
发布了62 篇原创文章 · 获赞 29 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/csdn_20150804/article/details/99200645