JUC集合类 LinkedBlockingDueue源码解析 JDK8

文章目录

  • 前言
  • 成员
  • 构造器
  • 入队操作
    • putFirst
    • putLast
  • 出队操作
    • takeFirst
    • takeLast
  • 删除内部节点
    • removeFirstOccurrence
    • removeLastOccurrence
  • 迭代器
  • 总结

前言

LinkedBlockingDueue是一种有界阻塞队列,它的底层是双向链表,所以它是双向的。也就是说,在队首队尾都可以进行插入和删除操作。这样,LinkedBlockingDueue就支持FIFO(队列)、FILO(栈)两种操作方式。

JUC框架 系列文章目录

成员

    static final class Node<E> {

        E item;
        Node<E> prev;
        Node<E> next;

        Node(E x) {
            item = x;
        }
    }

双向链表节点定义如上。
JUC集合类 LinkedBlockingDueue源码解析 JDK8_第1张图片
如上图展示了,可能的节点状态。

    transient Node<E> first;//队首
    transient Node<E> last;//队尾

    /** 大小,节点的个数 */
    private transient int count;

    /** 容量 */
    private final int capacity;

    final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();

注意,一个Lock,两个Condition。

构造器

    public LinkedBlockingDeque() {
        this(Integer.MAX_VALUE);
    }

    public LinkedBlockingDeque(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
    }

注意,初始化时,队列中连dummy node都没有的。所以刚初始化的队列的firstlast都为null。

入队操作

入队系列方法add、put、offer,实际都是在调用linkFirst或linkLast。仅以putFirst、putLast举例讲解。

putFirst

    public void putFirst(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//上来就尝试入队first端,如果失败就进入条件队列
            while (!linkFirst(node))//防止虚假唤醒
                notFull.await();
        } finally {
            lock.unlock();
        }
    }

函数上来就尝试入队first端,如果失败就进入条件队列。当从notFull.await()恢复执行时,再次尝试入队,如果失败则再次阻塞。

    private boolean linkFirst(Node<E> node) {
        // assert lock.isHeldByCurrentThread();
        if (count >= capacity)//size达到上限,只能返回false
            return false;
        Node<E> f = first;
        node.next = f;
        first = node;
        if (last == null)//如果队列为空(last才会为null),那么不用重连prev指针,只需要更新last
            last = node;
        else//如果队列非空,那么需要把旧队首的prev指针指好
            f.prev = node;
        ++count;
        notEmpty.signal();//既然入队成功,notEmpty这个条件就满足了
        return true;
    }

linkFirst入队成功返回true,失败返回false。只有队列非满,就一定能入队。

putLast

    public void putLast(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//上来就尝试入队last端,如果失败就进入条件队列
            while (!linkLast(node))//防止虚假唤醒
                notFull.await();
        } finally {
            lock.unlock();
        }
    }

函数上来就尝试入队last端,如果失败就进入条件队列。当从notFull.await()恢复执行时,再次尝试入队,如果失败则再次阻塞。

    private boolean linkLast(Node<E> node) {
        // assert lock.isHeldByCurrentThread();
        if (count >= capacity)//size达到上限,只能返回false
            return false;
        Node<E> l = last;
        node.prev = l;
        last = node;
        if (first == null)//如果队列为空(first才会为null),那么不用重连next指针,只需要更新first
            first = node;
        else//如果队列非空,那么需要把旧队尾的next指针指好
            l.next = node;
        ++count;
        notEmpty.signal();//既然入队成功,notEmpty这个条件就满足了
        return true;
    }

linkLast入队成功返回true,失败返回false。只有队列非满,就一定能入队。

出队操作

入队系列方法remove、take、poll,实际都是在调用unlinkFirst或unlinkLast。仅以takeFirst、takeLast举例讲解。

takeFirst

    public E takeFirst() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E x;
            //上来就尝试出队first端,如果失败就进入条件队列
            while ( (x = unlinkFirst()) == null)//防止虚假唤醒
                notEmpty.await();
            return x;
        } finally {
            lock.unlock();
        }
    }

函数上来就尝试出队first端,如果失败就进入条件队列。当从notEmpty.await()恢复执行时,再次尝试出队,如果失败则再次阻塞。

    private E unlinkFirst() {
        // assert lock.isHeldByCurrentThread();
        Node<E> f = first;
        if (f == null)//如果队列为空,直接返回null
            return null;
        Node<E> n = f.next;
        E item = f.item;//保存返回的值
        //先逻辑删除,再next指向自身
        f.item = null;
        f.next = f; // help GC
        first = n;//更新first为队首后继
        if (n == null)//如果队列之前只有一个节点,更新last
            last = null;
        else//如果队列之前不只有一个节点,让队首的prev为null
            n.prev = null;
        --count;
        notFull.signal();//既然出队成功,notFull这个条件就满足了
        return item;
    }

unlinkFirst出队成功返回非null,失败返回null。只有队列非空,就一定能出队。

takeLast

    public E takeLast() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E x;
            //上来就尝试出队last端,如果失败就进入条件队列
            while ( (x = unlinkLast()) == null)//防止虚假唤醒
                notEmpty.await();
            return x;
        } finally {
            lock.unlock();
        }
    }

函数上来就尝试出队last端,如果失败就进入条件队列。当从notEmpty.await()恢复执行时,再次尝试出队,如果失败则再次阻塞。

    private E unlinkLast() {
        // assert lock.isHeldByCurrentThread();
        Node<E> l = last;
        if (l == null)//如果队列为空,直接返回null
            return null;
        Node<E> p = l.prev;
        E item = l.item;//保存返回的值
        //先逻辑删除,再prev指向自身
        l.item = null;
        l.prev = l; // help GC
        last = p;//更新last为队尾前驱
        if (p == null)//如果队列之前只有一个节点,更新first
            first = null;
        else//如果队列之前不只有一个节点,让队尾的next为null
            p.next = null;
        --count;
        notFull.signal();//既然出队成功,notFull这个条件就满足了
        return item;
    }

unlinkLast出队成功返回非null,失败返回null。只有队列非空,就一定能出队。

删除内部节点

removeFirstOccurrence

    public boolean removeFirstOccurrence(Object o) {
        if (o == null) return false;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//从队首遍历到队尾
            for (Node<E> p = first; p != null; p = p.next) {
                if (o.equals(p.item)) {//找到了节点
                    unlink(p);
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

从队首遍历到队尾,如果找到了节点,则执行unlink

    void unlink(Node<E> x) {
        // assert lock.isHeldByCurrentThread();
        Node<E> p = x.prev;
        Node<E> n = x.next;
        if (p == null) {//如果是队首
            unlinkFirst();
        } else if (n == null) {//如果是队尾
            unlinkLast();
        } else {//如果是内部节点(此时队列中,必有三个节点,因为两个节点都会走上面的分支)
            p.next = n;
            n.prev = p;
            x.item = null;
            // 注意我们不会去操作内部删除节点的指针,这样方便迭代器继续使用。
            // 因为现在 从队列节点到内部删除节点 不可达,但内部删除节点到队列节点 可达
            --count;
            notFull.signal();
        }
    }

removeLastOccurrence

    public boolean removeLastOccurrence(Object o) {
        if (o == null) return false;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            for (Node<E> p = last; p != null; p = p.prev) {
                if (o.equals(p.item)) {
                    unlink(p);
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

完全对称,只是从队尾开始遍历。

迭代器

迭代器是弱一致性。

    private abstract class AbstractItr implements Iterator<E> {
        Node<E> next;//下一次next()的值提前保存

        E nextItem;//下一次next()的值提前保存

        private Node<E> lastRet;//用来实现remove

        abstract Node<E> firstNode();//抽象方法,获得遍历起点
        abstract Node<E> nextNode(Node<E> n);//抽象方法,根据前进方向获得下一个节点

        AbstractItr() {
            // set to initial position
            final ReentrantLock lock = LinkedBlockingDeque.this.lock;
            lock.lock();
            try {//初始化就准备好next的两个成员
                next = firstNode();
                nextItem = (next == null) ? null : next.item;
            } finally {
                lock.unlock();
            }
        }

        private Node<E> succ(Node<E> n) {
            for (;;) {
                Node<E> s = nextNode(n);
                if (s == null)//遍历到头了
                    return null;
                else if (s.item != null)//得到一个有效节点,则返回
                    return s;
                else if (s == n)//如果发送节点已经被unlink,则跳转到first
                    return firstNode();
                //执行到这里,n有后继,但n的item为null,所以后移n,继续找
                else
                    n = s;
            }
        }

        void advance() {
            final ReentrantLock lock = LinkedBlockingDeque.this.lock;
            lock.lock();
            try {
                // assert next != null;
                next = succ(next);//得到后继
                nextItem = (next == null) ? null : next.item;
            } finally {
                lock.unlock();
            }
        }

        public boolean hasNext() {
            return next != null;
        }

        public E next() {
            if (next == null)
                throw new NoSuchElementException();
            lastRet = next;
            E x = nextItem;
            advance();//准备下一个next()的数据,这个函数才开始加锁
            return x;
        }

        public void remove() {
            Node<E> n = lastRet;
            if (n == null)
                throw new IllegalStateException();
            lastRet = null;
            final ReentrantLock lock = LinkedBlockingDeque.this.lock;
            lock.lock();
            try {
                if (n.item != null)//如果它还没有被删除
                    unlink(n);
            } finally {
                lock.unlock();
            }
        }
    }

正向迭代器和反向迭代器只需要实现两个抽象方法。

    /** Forward iterator */
    private class Itr extends AbstractItr {
        Node<E> firstNode() { return first; }
        Node<E> nextNode(Node<E> n) { return n.next; }
    }

    /** Descending iterator */
    private class DescendingItr extends AbstractItr {
        Node<E> firstNode() { return last; }
        Node<E> nextNode(Node<E> n) { return n.prev; }
    }

总结

  • 和ArrayBlockingQueue一样,使用一个Lock和两个Condition来控制并发和阻塞。因为两端都可以入队出队,所以用一个锁才能保证正确。
  • 和LinkedBlockingQueue不同的是,我们初始化时,连dummy node也没有。
  • 从first端移除的节点,next指针指向自身,以区别于first指向节点(next指针不会指向自身,可能为真实节点或null)。从last端移除的节点同理。

你可能感兴趣的:(Java)