• 阻塞队列之五:LinkedBlockingQueue


    一、LinkedBlockingQueue简介

      LinkedBlockingQueue是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。采用对于的next构成链表的方式来存储对象。由于读只操作队头,而写只操作队尾,这里巧妙地采用了两把锁,对put和offer采用putLock,对take和poll采用takeLock,即为写锁和读锁,这两个锁实现阻塞(“two lock queue” algorithm)。避免了读写时相互竞争锁的现象,因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,因此LinkedBlockingQueue在高并发读写操作都多的情况下,性能会比ArrayBlockingQueue好很多在遍历以及删除元素时则要把两把锁都锁住

      可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。如果未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。

     注意1:容量范围可以在构造方法参数中指定作为防止队列过度扩展。如果未指定容量,则它等于 Integer.MAX_VALUE 
     注意2:它是线程安全的,是线程阻塞的。 
     注意3:不接受 null 元素 
     注意4:它实现了BlockingQueue接口。关于BlockingQueue,请参考《BlockingQueue》 
     注意5:它没有线程ArrayBlockingQueue那样的公平性设置。为什么这样设计呢?puzzle. 
     注意6:此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法

    二、LinkedBlockingQueue源码分析

    2.1、LinkedBlockingQueue的lock

      在LinkedBlockingQueue中有2个lock,分别是读锁和写锁,读写的的锁都是全局的,而且是final的。

    ArrayBlockingQueue只有1个锁,添加数据和删除数据的时候只能有1个被执行,不允许并行执行。而LinkedBlockingQueue有2个锁,写锁和读锁,添加数据和删除数据是可以并行进行的,当然添加数据和删除数据的时候只能有1个线程各自执行。

        /** 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();

    LinkedBlockingQueue使用ReentrantLock来实现的添加元素原子操作,具体可以看poll和offer中的阻塞awaitNanos(nanos)是使用了Condition中的AQS中的一个方法。

    2.2、成员变量

        /** 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
         */
        transient Node<E> head;
    
        /**
         * Tail of linked list.
         * Invariant: last.next == null
         */
        private transient Node<E> last;

    如图LinkedBlockingQueue中也有两个Node分别用来存放首尾节点,并且里面有个初始值为0的原子变量count用来记录队列元素个数。

    2.3、入队

      inkedBlockingQueue有不同的几个数据添加方法,add、offer、put方法,add方法内部调用offer方法。

    public boolean offer(E e) {
        if (e == null) throw new NullPointerException(); // 不允许空元素
        final AtomicInteger count = this.count;
        if (count.get() == capacity) // 如果容量满了,返回false
            return false;
        int c = -1;
        Node<E> node = new Node(e); // 容量没满,以新元素构造节点
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // 放锁加锁,保证调用offer方法的时候只有1个线程
        try {
            if (count.get() < capacity) { // 再次判断容量是否已满,因为可能拿锁在进行消费数据,没满的话继续执行
                enqueue(node); // 节点添加到链表尾部
                c = count.getAndIncrement(); // 元素个数+1
                if (c + 1 < capacity) // 如果容量还没满
                    notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满
            }
        } finally {
            putLock.unlock(); // 释放放锁,让其他线程可以调用offer方法
        }
        if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据
            signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费
        return c >= 0; // 添加成功返回true,否则返回false
    }

    put方法:

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException(); // 不允许空元素
        int c = -1;
        Node<E> node = new Node(e); // 以新元素构造节点
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly(); // 放锁加锁,保证调用put方法的时候只有1个线程
        try {
            while (count.get() == capacity) { // 如果容量满了
                notFull.await(); // 阻塞并挂起当前线程
            }
            enqueue(node); // 节点添加到链表尾部
            c = count.getAndIncrement(); // 元素个数+1
            if (c + 1 < capacity) // 如果容量还没满
                notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满
        } finally {
            putLock.unlock(); // 释放放锁,让其他线程可以调用put方法
        }
        if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据
            signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费
    }

    LinkedBlockingQueue的添加数据方法add,put,offer跟ArrayBlockingQueue一样,不同的是它们的底层实现不一样。

    ArrayBlockingQueue中放入数据阻塞的时候,需要消费数据才能唤醒。

    而LinkedBlockingQueue中放入数据阻塞的时候,因为它内部有2个锁,可以并行执行放入数据和消费数据,不仅在消费数据的时候进行唤醒插入阻塞的线程,同时在插入的时候如果容量还没满,也会唤醒插入阻塞的线程。

    enqueue()真正往单链表中增加元素的方法:

        private void enqueue(Node<E> node) {
            // assert putLock.isHeldByCurrentThread();
            // assert last.next == null;
            last = last.next = node;
        }

    2.4、出队

    LinkedBlockingQueue有不同的几个数据删除方法,poll、take、remove方法。

    poll方法:

    public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0) // 如果元素个数为0
            return null; // 返回null
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock(); // 拿锁加锁,保证调用poll方法的时候只有1个线程
        try {
            if (count.get() > 0) { // 判断队列里是否还有数据
                x = dequeue(); // 删除头结点
                c = count.getAndDecrement(); // 元素个数-1
                if (c > 1) // 如果队列里还有元素
                    notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费
            }
        } finally {
            takeLock.unlock(); // 释放拿锁,让其他线程可以调用poll方法
        }
        if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据
            signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据
                    return x;
    }

    take方法:

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly(); // 拿锁加锁,保证调用take方法的时候只有1个线程
        try {
            while (count.get() == 0) { // 如果队列里已经没有元素了
                notEmpty.await(); // 阻塞并挂起当前线程
            }
            x = dequeue(); // 删除头结点
            c = count.getAndDecrement(); // 元素个数-1
            if (c > 1) // 如果队列里还有元素
                notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费
        } finally {
            takeLock.unlock(); // 释放拿锁,让其他线程可以调用take方法
        }
        if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据
            signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据
        return x;
    }

    remove方法:

    public boolean remove(Object o) {
        if (o == null) return false;
        fullyLock(); // remove操作要移动的位置不固定,2个锁都需要加锁
        try {
            for (Node<E> trail = head, p = trail.next; // 从链表头结点开始遍历
                 p != null;
                 trail = p, p = p.next) {
                if (o.equals(p.item)) { // 判断是否找到对象
                    unlink(p, trail); // 修改节点的链接信息,同时调用notFull的signal方法
                    return true;
                }
            }
            return false;
        } finally {
            fullyUnlock(); // 2个锁解锁
        }
    }

    LinkedBlockingQueue的take方法对于没数据的情况下会阻塞,poll方法删除链表头结点,remove方法删除指定的对象。

    需要注意的是remove方法由于要删除的数据的位置不确定,需要2个锁同时加锁

        /**
         * Locks to prevent both puts and takes.
         */
        void fullyLock() {
            putLock.lock();
            takeLock.lock();
        }
    
        /**
         * Unlocks to allow both puts and takes.
         */
        void fullyUnlock() {
            takeLock.unlock();
            putLock.unlock();
        }

    真正出队的方法:

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

    2.5、peek操作

    获取但是不移除当前队列的头元素,没有则返回null

        public E peek() {
            //队列空,则返回null
            if (count.get() == 0)
                return null;
            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();
            }
        }

    2.6、size方法

    public int size() {
        return count.get();
    }

    2.7、完整源码

    public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {  
        /** 
         * 链表节点node类结构 
         */  
        static class Node<E> {  
            volatile E item;//volatile使得所有的write happen-befor read,保证了数据的可见性   
            Node<E> next;  
            Node(E x) { item = x; }  
        }  
        /** 队列容量,默认为Integer.MAX_VALUE*/  
        private final int capacity;  
        /** 用原子变量 表示当前元素的个数 */  
        private final AtomicInteger count = new AtomicInteger(0);  
        /** 表头节点 */  
        private transient Node<E> head;  
        /** 表尾节点 */  
        private transient Node<E> last;  
        /** 获取元素或删除元素时 要加的takeLock锁 */  
        private final ReentrantLock takeLock = new ReentrantLock();  
        /** 获取元素 notEmpty条件 */  
        private final Condition notEmpty = takeLock.newCondition();  
        /** 插入元素时 要加putLock锁 */  
        private final ReentrantLock putLock = new ReentrantLock();  
        /** 插入时,要判满 */  
        private final Condition notFull = putLock.newCondition();  
        /** 
         * 唤醒等待的take操作,在put/offer中调用(因为这些操作中不会用到takeLock锁) 
         */  
        private void signalNotEmpty() {  
            final ReentrantLock takeLock = this.takeLock;  
            takeLock.lock();  
            try {  
                notEmpty.signal();  
            } finally {  
                takeLock.unlock();  
            }  
        }  
        /** 
         * 唤醒等待插入操作,在take/poll中调用. 
         */  
        private void signalNotFull() {  
            final ReentrantLock putLock = this.putLock;  
            putLock.lock();  
            try {  
                notFull.signal();  
            } finally {  
                putLock.unlock();  
            }  
        }  
        /** 
         * 插入到尾部 
         */  
        private void insert(E x) {  
            last = last.next = new Node<E>(x);  
        }  
        /** 
         * 获取并移除头元素 
         */  
        private E extract() {  
            Node<E> first = head.next;  
            head = first;  
            E x = first.item;  
            first.item = null;  
            return x;  
        }  
        /** 
         * 锁住两把锁,在remove,clear等方法中调用 
         */  
        private void fullyLock() {  
            putLock.lock();  
            takeLock.lock();  
        }  
        /** 
         * 和fullyLock成对使用 
         */  
        private void fullyUnlock() {  
            takeLock.unlock();  
            putLock.unlock();  
        }  
        /** 
         * 默认构造,容量为 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);  
        }  
        /** 
         * 指定初始化集合的构造 
         */  
        public LinkedBlockingQueue(Collection<? extends E> c) {  
            this(Integer.MAX_VALUE);  
            for (E e : c)  
                add(e);  
        }  
        /** 
         * 通过原子变量,直接获得大小 
         */  
        public int size() {  
            return count.get();  
        }  
        /** 
         *返回理想情况下(没有内存和资源约束)此队列可接受并且不会被阻塞的附加元素数量。 
         */  
        public int remainingCapacity() {  
            return capacity - count.get();  
        }  
        /** 
         * 将指定元素插入到此队列的尾部,如有必要,则等待空间变得可用。 
         */  
        public void put(E e) throws InterruptedException {  
            if (e == null) throw new NullPointerException();  
            int c = -1;  
            final ReentrantLock putLock = this.putLock;  
            final AtomicInteger count = this.count;  
            putLock.lockInterruptibly();  
            try {  
                try {  
                    while (count.get() == capacity)  
                        notFull.await();  
                } catch (InterruptedException ie) {  
                    notFull.signal(); // propagate to a non-interrupted thread  
                    throw ie;  
                }  
                insert(e);  
                c = count.getAndIncrement();  
                if (c + 1 < capacity)  
                    notFull.signal();  
            } finally {  
                putLock.unlock();  
            }  
            if (c == 0)  
                signalNotEmpty();  
        }  
        /** 
         * 将指定元素插入到此队列的尾部,如有必要,则等待指定的时间以使空间变得可用。 
         */  
        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 {  
                for (;;) {  
                    if (count.get() < capacity) {  
                        insert(e);  
                        c = count.getAndIncrement();  
                        if (c + 1 < capacity)  
                            notFull.signal();  
                        break;  
                    }  
                    if (nanos <= 0)  
                        return false;  
                    try {  
                        nanos = notFull.awaitNanos(nanos);  
                    } catch (InterruptedException ie) {  
                        notFull.signal(); // propagate to a non-interrupted thread  
                        throw ie;  
                    }  
                }  
            } finally {  
                putLock.unlock();  
            }  
            if (c == 0)  
                signalNotEmpty();  
            return true;  
        }  
        /** 
         *将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量), 
         *在成功时返回 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;  
            final ReentrantLock putLock = this.putLock;  
            putLock.lock();  
            try {  
                if (count.get() < capacity) {  
                    insert(e);  
                    c = count.getAndIncrement();  
                    if (c + 1 < capacity)  
                        notFull.signal();  
                }  
            } finally {  
                putLock.unlock();  
            }  
            if (c == 0)  
                signalNotEmpty();  
            return c >= 0;  
        }  
        //获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。  
        public E take() throws InterruptedException {  
            E x;  
            int c = -1;  
            final AtomicInteger count = this.count;  
            final ReentrantLock takeLock = this.takeLock;  
            takeLock.lockInterruptibly();  
            try {  
                try {  
                    while (count.get() == 0)  
                        notEmpty.await();  
                } catch (InterruptedException ie) {  
                    notEmpty.signal(); // propagate to a non-interrupted thread  
                    throw ie;  
                }  
                x = extract();  
                c = count.getAndDecrement();  
                if (c > 1)  
                    notEmpty.signal();  
            } finally {  
                takeLock.unlock();  
            }  
            if (c == capacity)  
                signalNotFull();  
            return x;  
        }  
          
        //获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要  
        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 {  
                for (;;) {  
                    if (count.get() > 0) {  
                        x = extract();  
                        c = count.getAndDecrement();  
                        if (c > 1)  
                            notEmpty.signal();  
                        break;  
                    }  
                    if (nanos <= 0)  
                        return null;  
                    try {  
                        nanos = notEmpty.awaitNanos(nanos);  
                    } catch (InterruptedException ie) {  
                        notEmpty.signal(); // propagate to a non-interrupted thread  
                        throw ie;  
                    }  
                }  
            } finally {  
                takeLock.unlock();  
            }  
            if (c == capacity)  
                signalNotFull();  
            return x;  
        }  
          
        //获取并移除此队列的头,如果此队列为空,则返回 null。  
        public E poll() {  
            final AtomicInteger count = this.count;  
            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 = extract();  
                    c = count.getAndDecrement();  
                    if (c > 1)  
                        notEmpty.signal();  
                }  
            } finally {  
                takeLock.unlock();  
            }  
            if (c == capacity)  
                signalNotFull();  
            return x;  
        }  
        //获取但不移除此队列的头;如果此队列为空,则返回 null。  
        public E peek() {  
            if (count.get() == 0)  
                return null;  
            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();  
            }  
        }  
        /** 
         * 从此队列移除指定元素的单个实例(如果存在)。 
         */  
        public boolean remove(Object o) {  
            if (o == null) return false;  
            boolean removed = false;  
            fullyLock();  
            try {  
                Node<E> trail = head;  
                Node<E> p = head.next;  
                while (p != null) {  
                    if (o.equals(p.item)) {  
                        removed = true;  
                        break;  
                    }  
                    trail = p;  
                    p = p.next;  
                }  
                if (removed) {  
                    p.item = null;  
                    trail.next = p.next;  
                    if (last == p)  
                        last = trail;  
                    if (count.getAndDecrement() == capacity)  
                        notFull.signalAll();  
                }  
            } finally {  
                fullyUnlock();  
            }  
            return removed;  
        }  
        ……  
    }  
    View Code

    三、使用示例

  • 相关阅读:
    Identity Server 4 原理和实战(完结)_建立Angular 客户端
    Identity Server 4 原理和实战(完结)_为 MVC 客户端刷新 Token
    Identity Server 4 原理和实战(完结)_Authorization Code Flow 实例
    Identity Server 4 原理和实战(完结)_Resource Owner Password Credentials 授权实例
    ASP.NET Core会议管理平台实战_2、基本概念的理解
    ASP.NET Core会议管理平台实战_汇总贴
    ASP.NET Core会议管理平台实战_1、开篇介绍
    Identity Server 4 原理和实战(完结)_建立Identity Server 4项目,Client Credentials 授权实例
    Identity Server 4 原理和实战(完结)_----选看 OpenId Connect 简介
    Identity Server 4 原理和实战(完结)_----选看 OAuth 2.0 简介(下)
  • 原文地址:https://www.cnblogs.com/duanxz/p/3202066.html
Copyright © 2020-2023  润新知