• 并发包中的并发队列


    一、ConcurrentLinkedQueue——无界非阻塞队列(链式存储)

      

     ConcurrentLinkedQueue内部的队列是单链表实现的,

    1.链表节点:静态内部类Node,没有其他设计,仅unsafe保证原子性

        private static class Node<E> {
            volatile E item;
            volatile Node<E> next;
    
            Node(E item) {
                UNSAFE.putObject(this, itemOffset, item);
            }
    
            boolean casItem(E cmp, E val) {
                return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
            }
    
            void lazySetNext(Node<E> val) {
                UNSAFE.putOrderedObject(this, nextOffset, val);
            }
    
            boolean casNext(Node<E> cmp, Node<E> val) {
                return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
            }
    
            private static final sun.misc.Unsafe UNSAFE;
            private static final long itemOffset;
            private static final long nextOffset;
    
            static {
                try {
                    UNSAFE = sun.misc.Unsafe.getUnsafe();
                    Class<?> k = Node.class;
                    itemOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("item"));
                    nextOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("next"));
                } catch (Exception e) {
                    throw new Error(e);
                }
            }
        }

    2.变量与构造方法

        private transient volatile Node<E> head;//单链表头节点
        private transient volatile Node<E> tail;//单链表尾节点
    
        public ConcurrentLinkedQueue() {
            head = tail = new Node<E>(null);
        }
    
        public ConcurrentLinkedQueue(Collection<? extends E> c) {
            Node<E> h = null, t = null;
            for (E e : c) {
                checkNotNull(e);
                Node<E> newNode = new Node<E>(e);
                if (h == null)
                    h = t = newNode;
                else {
                    t.lazySetNext(newNode);
                    t = newNode;
                }
            }
            if (h == null)
                h = t = new Node<E>(null);
            head = h;
            tail = t;
        }

    3.方法

    bealoon offer(E e):入队

        public boolean offer(E e) {
            //非空检查
            checkNotNull(e);
            //创建节点Node对象,构造方法中unsafe保证原子性
            final Node<E> newNode = new Node<E>(e);
            //从尾节点入队
            for (Node<E> t = tail, p = t;;) {
                Node<E> q = p.next;
                if (q == null) {
                    // q == null 说明p是尾节点 CAS入队
                    if (p.casNext(null, newNode)) {
                        //与下面else,两次offer后tail指向尾节点
                        if (p != t) // hop two nodes at a time
                            casTail(t, newNode);  // Failure is OK.
                        return true;
                    }
                    // Lost CAS race to another thread; re-read next
                }
                else if (p == q)
                    //poll引起的
                    p = (t != (t = tail)) ? t : head;
                else
                    // Check for tail upda tes after two hops.
                    p = (p != t && t != (t = tail)) ? t : q;
            }
        }

     入队过程:offer两次后,tail才会指向尾节点

    E poll():出队

        public E poll() {
            restartFromHead:
            for (;;) {
                for (Node<E> h = head, p = h, q;;) {
                    E item = p.item;
    
                    if (item != null && p.casItem(item, null)) {
                        // Successful CAS is the linearization point
                        // for item to be removed from this queue.
                        if (p != h) // hop two nodes at a time
                            updateHead(h, ((q = p.next) != null) ? q : p);
                        return item;
                    }
                    else if ((q = p.next) == null) {
                        updateHead(h, p);
                        return null;
                    }
                    else if (p == q)
                        continue restartFromHead;
                    else
                        p = q;
                }
            }
        }

    出队,三种情景

    1)空节点时,不变

    2)一个节点时,头节点自循环,也就对应offer中p == q的情景

    3)多个节点时,头节点自循环,head指针会一次跳过两个节点。

     E peek():获取队首节点,不删除,但是第一次调用时会删除哨兵节点(初始化head、tail指向的空节点)

        public E peek() {
            restartFromHead:
            for (;;) {
                for (Node<E> h = head, p = h, q;;) {
                    E item = p.item;
                    if (item != null || (q = p.next) == null) {
                        updateHead(h, p);
                        return item;
                    }
                    else if (p == q)
                        continue restartFromHead;
                    else
                        p = q;
                }
            }
        }

    remove存在元素就删除第一个该元素

    size和contains方法在并发环境下不常用,因为CAS未加锁,会导致结果不精确(保证最终一致性,不保证实时一致性)

    二、LinkedBlockingQueue——有界阻塞队列(链式存储)

     

        static class Node<E> {
            E item;
            Node<E> next;
            Node(E x) { item = x; }
        }

    2.变量与构造方法

        /** 链表容量 */
        private final int capacity;
    
        /** 队列中元素数 */
        private final AtomicInteger count = new AtomicInteger();
    
        /**
         * 头节点(head.item == null)
         */
        transient Node<E> head;
    
        /**
         * 尾节点(last.item == null)
         */
        private transient Node<E> last;
    
        /** 出队独占锁  用于take、poll方法中 */
        private final ReentrantLock takeLock = new ReentrantLock();
    
        /** 条件变量 take的阻塞队列 */
        private final Condition notEmpty = takeLock.newCondition();
    
        /** 入队独占锁  用于put、offer中 */
        private final ReentrantLock putLock = new ReentrantLock();
    
        /** 条件变量 put的阻塞队列 */
        private final Condition notFull = putLock.newCondition();
    
        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);
            final ReentrantLock putLock = this.putLock;
            putLock.lock(); // Never contended, but necessary for visibility
            try {
                int n = 0;
                for (E e : c) {
                    if (e == null)
                        throw new NullPointerException();
                    if (n == capacity)
                        throw new IllegalStateException("Queue full");
                    enqueue(new Node<E>(e));
                    ++n;
                }
                count.set(n);
            } finally {
                putLock.unlock();
            }
        }

    3.方法

    1)boolean offer(E e):非阻塞入队

        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();
                    if (c + 1 < capacity)
                        //队列不满,唤醒notFull条件队列中的一个线程
                        notFull.signal();
                }
            } finally {
                putLock.unlock();
            }
            if (c == 0)
                //队列为空时,唤醒notEmpty条件队列中的一个线程
                signalNotEmpty();
            return c >= 0;
        }

     2)void put(E e):阻塞入队

        public void put(E e) throws InterruptedException {
            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 {
                while (count.get() == capacity) {
                    //队满时,当前线程入notFull条件队列
                    notFull.await();
                }
                enqueue(node);
                c = count.getAndIncrement();
                if (c + 1 < capacity)
                    //入队后,队列不满,唤醒因队满引起的阻塞线程(put方法引起)
                    notFull.signal();
            } finally {
                putLock.unlock();
            }
            if (c == 0)
                //队空时,唤醒因notEmpty条件队列中的一个阻塞线程
                signalNotEmpty();
        }

    3)E poll():不阻塞出队

        public E poll() {
            final AtomicInteger count = this.count;
            if (count.get() == 0)
                //队列为空直接返回null,不入阻塞队列
                return null;
            E x = null;
            int c = -1;
            final ReentrantLock takeLock = this.takeLock;
            takeLock.lock();
            try {
                if (count.get() > 0) {
                    x = dequeue();
                    c = count.getAndDecrement();
                    if (c > 1)
                        //队列不为空时,唤醒notEmpty条件队列中的一个线程
                        notEmpty.signal();
                }
            } finally {
                takeLock.unlock();
            }
            if (c == capacity)
                //出队成功后,队满,欢迎notFull条件队列中的一个线程
                signalNotFull();
            return x;
        }

    4)E 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条件阻塞队列,阻塞
                    notEmpty.await();
                }
                x = dequeue();
                c = count.getAndDecrement();
                if (c > 1)
                    //出队后,队不为空,唤醒notEmpty条件阻塞队列中的一个线程
                    notEmpty.signal();
            } finally {
                takeLock.unlock();
            }
            if (c == capacity)
                //队满时,唤醒notFull条件阻塞队列中的一个线程
                signalNotFull();
            return x;
        }

    5)E peek()非阻塞获取队首元素

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

    6)boolean remove( Object o):删除队列中指定元素

        public boolean remove(Object o) {
            if (o == null) return false;
            //获取两个锁,takeLock及putLock,即会阻塞出入队操作
            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();
            }
        }

    7).int size():队列元素个数

        public int size() {
            return count.get();//takeLock和putLock已经保证了count的原子性
        }

    4.总结

    1)单链表实现,

    2)读写分离策略,引入两个可重入独占锁takeLock(出队锁)和putLock(入队锁)

    3)提供入队的非阻塞方法offer和阻塞方法put,队满,调用offer入队时直接丢弃,调用put入队时,线程入putLock的条件变量notFull的阻塞队列,offer、put、poll、take都会唤醒notFull的          阻塞队列中的线程

          另offer方法忽略中断引起的返回,put方法不忽略中断引起的返回,会抛出异常

    4)提供出队的非阻塞方法poll和阻塞方法take,队空,调用poll出队时直接返回null,调用take出队时,线程入takeLock的条件变量notEmpty的阻塞队列,offer、put、poll、take都会唤醒            notEmpty的阻塞队列中的线程

          另poll方法忽略中断引起的返回,take方法不忽略中断引起的返回,会抛出异常

     5)remove操作会获取双重锁,即remove操作时,会阻塞其他出入队线程。

     6)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列满空入条件队列(生产消费模型)

    三、ArrayBlockingQueue——有界阻塞队列(顺序存储)

     1.变量与构造方法

        /** 存放队列元素的数组——顺序存储 */
        final Object[] items;
    
        /** 队列头节点数组下标 items index for next take, poll, peek or remove */
        int takeIndex;
    
        /**  入队时入队元素存入的数组下标(尾节点数组下标+1,注意循环队列顺序实现) items index for next put, offer, or add */
        int putIndex;
    
        /** 队列中元素个数 Number of elements in the queue */
        int count;
    
        /*
         * 并发控制采用单锁双条件控制 
         */
    
        /**  Main lock guarding all access */
        final ReentrantLock lock;
    
        /** 出队时 队空,条件阻塞队列 Condition for waiting takes */
        private final Condition notEmpty;
    
        /** 入队时 队满,条件阻塞队列 Condition for waiting puts */
        private final Condition notFull;
    
        /**
         * 迭代器
         */
        transient Itrs itrs = null;
    
        public ArrayBlockingQueue(int capacity) {
            this(capacity, false);
        }
    
        /**构造方法初始化锁及条件变量*/
        public ArrayBlockingQueue(int capacity, boolean fair) {
            if (capacity <= 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity];
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
        }
    
        public ArrayBlockingQueue(int capacity, boolean fair,
                                  Collection<? extends E> c) {
            this(capacity, fair);
    
            final ReentrantLock lock = this.lock;
            lock.lock(); // Lock only for visibility, not mutual exclusion
            try {
                int i = 0;
                try {
                    for (E e : c) {
                        checkNotNull(e);
                        items[i++] = e;
                    }
                } catch (ArrayIndexOutOfBoundsException ex) {
                    throw new IllegalArgumentException();
                }
                count = i;
                putIndex = (i == capacity) ? 0 : i;
            } finally {
                lock.unlock();
            }
        }

    2.方法

    1)boolean offer(E e):非阻塞入队

        public boolean offer(E e) {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if (count == items.length)
                    //队满直接返回,未入条件阻塞队列--非阻塞
                    return false;
                else {
                    enqueue(e);
                    return true;
                }
            } finally {
                lock.unlock();
            }
        }
    
        private void enqueue(E x) {
            final Object[] items = this.items;
            items[putIndex] = x;
            //数组实现的循环队列
            if (++putIndex == items.length)
                putIndex = 0;
            count++;
            //唤醒notEmpty条件阻塞队列中的一个线程
            notEmpty.signal();
        }

    2)void put(E e):阻塞入队

        public void put(E e) throws InterruptedException {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
            //不忽略中断引起的AQS阻塞队列的返回,抛出异常
            lock.lockInterruptibly();
            try {
                while (count == items.length)
                    //队满入notFull条件阻塞队列--阻塞
                    notFull.await();
                enqueue(e);
            } finally {
                lock.unlock();
            }
        }

    3)E poll():非阻塞出队

        public E poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return (count == 0) ? null : dequeue();
            } finally {
                lock.unlock();
            }
        }
    
        private E dequeue() {
            final Object[] items = this.items;
            @SuppressWarnings("unchecked")
            E x = (E) items[takeIndex];
            items[takeIndex] = null;
            //数组实现的循环队列,头指针变化
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            if (itrs != null)
                //迭代器中元素出队
                itrs.elementDequeued();
            //唤醒notFull条件阻塞队列中的一个线程
            notFull.signal();
            return x;
        }

    4)E take():阻塞出队

        public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            //不忽略中断引起的AQS阻塞队列中线程的返回
            lock.lockInterruptibly();
            try {
                while (count == 0)
                    //队空时,入notEmpty条件阻塞队列--阻塞
                    notEmpty.await();
                return dequeue();
            } finally {
                lock.unlock();
            }
        }        

    5)E peek():非阻塞获取队首元素

        public E peek() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return itemAt(takeIndex); // null when queue is empty
            } finally {
                lock.unlock();
            }
        }
    
        final E itemAt(int i) {
            return (E) items[i];
        }

    6)int size():当前队列中元素个数

        public int size() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return count;//加锁原因:count内存可见性(未被volatile修饰)
            } finally {
                lock.unlock();
            }
        }

    三、总结

    1.循环队列实现

    2.单锁双条件变量的设计可能是由于读写操作的是同一个数组,与LinkedBlockingQueue双锁双条件变量区别,操作的是两个独立的节点

    3)提供入队的非阻塞方法offer和阻塞方法put,队满,调用offer入队时直接丢弃,调用put入队时,线程入lock的条件变量notFull的阻塞队列,poll、take都会唤醒notFull的阻塞队列中的线程,另offer方法忽略中断引起的返回,put方法不忽略中断引起的返回,会抛出异常

    4)提供出队的非阻塞方法poll和阻塞方法take,队空,调用poll出队时直接返回null,调用take出队时,线程入takeLock的条件变量notEmpty的阻塞队列,offer、put都会唤醒notEmpty的阻塞队列中的线程,另poll方法忽略中断引起的返回,take方法不忽略中断引起的返回,会抛出异常

    5)size方法加锁,比LinkedBlockingQueue的size方法更加精确

    6)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列满空入条件队列(生产消费模型),(循环队列,将数据画成圆形比较好,后补)

    四、PriorityBlockingQueue——优先级无界阻塞队列(顺序存储、堆实现)

    顺序存储实现的平衡二叉树堆结构,用堆来实现优先级,涉及比较所以元素需要实现Comparable接口

    1、变量与构造方法

        /**
         * 默认队列大小
         */
        private static final int DEFAULT_INITIAL_CAPACITY = 11;
    
        /**
         * 队列最大容量
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        /**
         * 实际存放队列元素的数组,大小可扩容
         */
        private transient Object[] queue;
    
        /**
         * 队列中元素的个数
         */
        private transient int size;
    
        /**
         * 比较器
         */
        private transient Comparator<? super E> comparator;
    
        /**
         * 独占锁
         */
        private final ReentrantLock lock;
    
        /**
         * 为空时条件阻塞队列
         */
        private final Condition notEmpty;
    
        /**
         * 自旋锁标识 扩容时使用
         */
        private transient volatile int allocationSpinLock;
    
        /**
         * 优先级队列、仅用于序列化
         */
        private PriorityQueue<E> q;
    
        /**
         * 默认构造*/
        public PriorityBlockingQueue() {
            this(DEFAULT_INITIAL_CAPACITY, null);
        }
    
        public PriorityBlockingQueue(int initialCapacity) {
            this(initialCapacity, null);
        }
    
        public PriorityBlockingQueue(int initialCapacity,
                                     Comparator<? super E> comparator) {
            if (initialCapacity < 1)
                throw new IllegalArgumentException();
            this.lock = new ReentrantLock();//初始化独占锁
            this.notEmpty = lock.newCondition();//初始化为空时条件变量(无界的所以没有队满时条件变量)
            this.comparator = comparator;//初始化比较器
            this.queue = new Object[initialCapacity];//初始化数组
        }
    
        public PriorityBlockingQueue(Collection<? extends E> c) {
            this.lock = new ReentrantLock();
            this.notEmpty = lock.newCondition();
            boolean heapify = true; // true if not known to be in heap order
            boolean screen = true;  // true if must screen for nulls
            if (c instanceof SortedSet<?>) {
                SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
                this.comparator = (Comparator<? super E>) ss.comparator();
                heapify = false;
            }
            else if (c instanceof PriorityBlockingQueue<?>) {
                PriorityBlockingQueue<? extends E> pq =
                    (PriorityBlockingQueue<? extends E>) c;
                this.comparator = (Comparator<? super E>) pq.comparator();
                screen = false;
                if (pq.getClass() == PriorityBlockingQueue.class) // exact match
                    heapify = false;
            }
            Object[] a = c.toArray();
            int n = a.length;
            // If c.toArray incorrectly doesn't return Object[], copy it.
            if (a.getClass() != Object[].class)
                a = Arrays.copyOf(a, n, Object[].class);
            if (screen && (n == 1 || this.comparator != null)) {
                for (int i = 0; i < n; ++i)
                    if (a[i] == null)
                        throw new NullPointerException();
            }
            this.queue = a;
            this.size = n;
            if (heapify)
                heapify();
        }

     2、常用方法

    1)由于是无界队列所以元素入队不存在阻塞,offer(E e),put(E e)都是非阻塞的

        public boolean offer(E e) {
            if (e == null)
                throw new NullPointerException();
            final ReentrantLock lock = this.lock;
            lock.lock();
            int n, cap;
            Object[] array;
            while ((n = size) >= (cap = (array = queue).length))
                //队列元素大小超过队列大小时候数组扩容
                tryGrow(array, cap);
            try {
                Comparator<? super E> cmp = comparator;
                if (cmp == null)
                    //比较器为空默认元素e.compareTo(array[]),建堆与入堆调整
                    siftUpComparable(n, e, array);
                else
                    //比较器compareTo(e,array[]),建堆与入堆调整
                    siftUpUsingComparator(n, e, array, cmp);
                size = n + 1;
                //入队成功,唤醒notEmpty条件阻塞队列中的一个线程
                notEmpty.signal();
            } finally {
                lock.unlock();
            }
            return true;
        }

    2)E poll():非阻塞出队

        public E poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return dequeue();
            } finally {
                lock.unlock();
            }
        }
    
        private E dequeue() {
            int n = size - 1;
            if (n < 0)
                return null;
            else {
                Object[] array = queue;
                E result = (E) array[0];
                E x = (E) array[n];
                array[n] = null;
                Comparator<? super E> cmp = comparator;
                if (cmp == null)
                    siftDownComparable(0, x, array, n);//堆顶出队,然后调整堆
                else
                    siftDownUsingComparator(0, x, array, n, cmp);
                size = n;
                return result;
            }
        }

    3)E take():阻塞出队

        public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            //不忽略中断引起的AQS阻塞队列的返回,会抛出异常
            lock.lockInterruptibly();
            E result;
            try {
                while ( (result = dequeue()) == null)
                    //队列为空,入notEmpty条件阻塞队列
                    notEmpty.await();
            } finally {
                lock.unlock();
            }
            return result;
        }

    4)E peek():非阻塞获取队首元素

        public E peek() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return (size == 0) ? null : (E) queue[0];
            } finally {
                lock.unlock();
            }
        }

    5)int size():获取队列中元素个数

        public int size() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return size;
            } finally {
                lock.unlock();
            }
        }

    3.两个重要的实现方法

    1)数组扩容方法tryGrow

        private void tryGrow(Object[] array, int oldCap) {
             //扩容时先释放锁,不释放会导致扩容线程扩容期间,其他线程操作会阻塞
            lock.unlock(); // must release and then re-acquire main lock
            Object[] newArray = null;
            //allocationSpinLock标识实现自旋锁,1表示正在扩容
            if (allocationSpinLock == 0 &&
                UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                         0, 1)) {
                try {
                    //扩容策略①当前数组大小小于64时 newCap = 2 * oldCap + 2
                    //扩容策略②当前数组大小不小于64时 newCap = 1.5 * oldCap
                    int newCap = oldCap + ((oldCap < 64) ?
                                           (oldCap + 2) : // grow faster if small
                                           (oldCap >> 1));
                    if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                        //先数组newCap大于扩容最大值,oldCap+1还是溢出的话抛出异常,否则newCap = 最大容量
                        int minCap = oldCap + 1;
                        if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                            throw new OutOfMemoryError();
                        newCap = MAX_ARRAY_SIZE;
                    }
                    //扩容期间 数组被其他线程修改,扩容失败,offer方法中while循环重试
                    if (newCap > oldCap && queue == array)
                        newArray = new Object[newCap];
                } finally {
                    allocationSpinLock = 0;
                }
            }
            if (newArray == null) //正在扩容时,其他线程让出CPU back off if another thread is allocating
                Thread.yield();
            //还原执行扩容的线程的锁状态
            lock.lock();
            if (newArray != null && queue == array) {
                queue = newArray;
                System.arraycopy(array, 0, newArray, 0, oldCap);
            }
        }

    2)堆实现

    建堆方法siftUpComparable

        private static <T> void siftUpComparable(int k, T x, Object[] array) {
            Comparable<? super T> key = (Comparable<? super T>) x;
            while (k > 0) {
                int parent = (k - 1) >>> 1;
                Object e = array[parent];
                if (key.compareTo((T) e) >= 0)
                    break;
                array[k] = e;
                k = parent;
            }
            array[k] = key;
        }

    例子:入堆序列15324,小顶堆

     

    堆顶元素出堆siftDownComparable

        private static <T> void siftDownComparable(int k, T x, Object[] array,
                                                   int n) {
            if (n > 0) {
                Comparable<? super T> key = (Comparable<? super T>)x;
                int half = n >>> 1;           // loop while a non-leaf
                while (k < half) {
                    int child = (k << 1) + 1; // assume left child is least
                    Object c = array[child];
                    int right = child + 1;
                    if (right < n &&
                        ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                        c = array[child = right];
                    if (key.compareTo((T) c) <= 0)
                        break;
                    array[k] = c;
                    k = child;
                }
                array[k] = key;
            }
        }

    例子:入堆序列15324,小顶堆出堆

     

     4、总结

    1.顺序存储,二叉平衡树堆实现

    2.由于是无界的所以仅有一个条件变量notEmpty,单锁单条件变量

    3)入堆方法offer、put是非阻塞的(无notFull)

    4)提供出队的非阻塞方法poll和阻塞方法take,队空,调用poll出队时直接返回null,调用take出队时,线程入lock的条件变量notEmpty的阻塞队列,offer、put都会唤醒notEmpty的阻塞队列中的线程,另poll方法忽略中断引起的返回,take方法不忽略中断引起的返回,会抛出异常

    5)无界阻塞队列,所以存在扩容机制;用自旋锁保证扩容时的线程安全;扩容策略:

    ①oldCap<64时newCap = 2*oldCap +2;oldCap>=64时newCap = 1.5*oldCap;

    ②newCap>maxCap时oldCap+1>maxCap抛错;否则newCap=maxCap;

    6)建堆与堆调整是《数据结构》中堆排序算法的简化。

    7)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列空入条件队列(生产消费模型),数组中的元素不再是固定的,每次入队出队都会进行堆调整

    五、DelayQueue——无界阻塞延时队列

     采用PriorityQueue存储元素,延时队列故元素必须有个过期时间(必须实现Delayed接口)

    public interface Delayed extends Comparable<Delayed> {
        long getDelay(TimeUnit unit);
    }

    1、变量与构造方法

        private final transient ReentrantLock lock = new ReentrantLock();
        private final PriorityQueue<E> q = new PriorityQueue<E>();
    
        /**
         * leader-follower线程模型的变体*/
        private Thread leader = null;
    
        /**
         * 争取leader线程的阻塞队列
         */
        private final Condition available = lock.newCondition();
    
        public DelayQueue() {}
    
        public DelayQueue(Collection<? extends E> c) {
            this.addAll(c);
        }

    2、方法

    1)无界队列,offer()、put()是非阻塞方法

        public boolean offer(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                q.offer(e);
                if (q.peek() == e) {
                    //元素入队且位于队首;由于leader线程与队首元素有关,队首元素改变了,需要重新设置
                    leader = null;
                    available.signal();
                }
                return true;
            } finally {
                lock.unlock();
            }
        }

    2)E poll():非阻塞出队;E peek():获取队首元素

        public E poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                E first = q.peek();
                if (first == null || first.getDelay(NANOSECONDS) > 0)
                    //没有过期元素时,直接返回——非阻塞
                    return null;
                else
                    return q.poll();
            } finally {
                lock.unlock();
            }
        }
    
        public E peek() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return q.peek();
            } finally {
                lock.unlock();
            }
        }

    3)E take():阻塞出队

        public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    E first = q.peek();
                    if (first == null)
                        //队列为空,入条件阻塞队列  和前面的notEmpty一样
                        available.await();
                    else {
                        long delay = first.getDelay(NANOSECONDS);
                        if (delay <= 0)
                            //队首元素过期则出队
                            return q.poll();
                        first = null; // don't retain ref while waiting
                        //队首元素未过期
                        if (leader != null)
                            //leader线程不为空,当前线程为followed线程入条件阻塞队列  比notEmpty多行使的职责
                            available.await();
                        else {
                            //leader线程为空,设置当前线程为leader线程
                            Thread thisThread = Thread.currentThread();
                            leader = thisThread;
                            try {
                                //leader线程入条件阻塞队列,有效期为过期时间,
                                //此时入队线程会被lock阻塞入AQS阻塞队列,出队线程会被leader线程阻塞,进入available条件阻塞队列
                                available.awaitNanos(delay);
                            } finally {
                                //过期时间到,取消leader线程,线程出队
                                if (leader == thisThread)
                                    leader = null;
                            }
                        }
                    }
                }
            } finally {
                if (leader == null && q.peek() != null)
                    //出队成功后  leader线程为空且队列不为空,唤醒其他出队线程(followed线程)
                    available.signal();
                lock.unlock();
            }
        }

    3.总结

    1).由优先级队列PriorityQueue存储数据,PriorityQueue是java.util下的集合,不是线程安全的,用独占锁保证线程安全;入队元素需要实现Delayed接口

    2).单锁单条件变量available;这里的条件变量available两种情景

    ①队列为空时,take出队线程入条件阻塞队列(与notEmpty场景一致)

    ②队列不为空时,take出队但没有过期元素,入条件阻塞队列

    3).leader-followed线程模型的简易变体,队列不为空且多个take线程但没有过期元素,线程全部入条件阻塞队列,并会设置一个线程为leader线程,其他线程为followed线程;

    leader线程由于awaitNanos(delay)出队成功后会唤醒其他followed线程;争取leader线程

    4)两种阻塞:因获取Lock锁入AQS阻塞队列;获取Lock锁后,因队列空或没有过期元素入条件队列(生产消费模型)

     4、实例

    public class DelayQueueTest {
    
        public static void main(String[] args){
            DelayQueue<DelayedEle> delayQueue = new DelayQueue<>();
            Random random = new Random();
            for (int i = 0; i < 10; i++) {
                DelayedEle ele = new DelayedEle(random.nextInt(500), "task" + i);
                delayQueue.offer(ele);
            }
            DelayedEle eleTake = null;
            try {
                for (;;){
                    while ((eleTake = delayQueue.take()) != null){
                        System.out.println(eleTake.toString());
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    
        static class DelayedEle implements Delayed {
    
            private final long delayTime;
            private final long expire;
            private String taskName;
    
            public DelayedEle(long delayTime, String taskName){
                this.delayTime = delayTime;
                this.taskName = taskName;
                this.expire = System.currentTimeMillis() + delayTime;
            }
    
            @Override
            public long getDelay(TimeUnit unit) {
                return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
            }
    
            @Override
            public int compareTo(Delayed o) {
                return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
            }
    
            @Override
            public String toString() {
                final StringBuilder sb = new StringBuilder("DelayedEle{");
                sb.append("delay=").append(delayTime);
                sb.append(", expire=").append(expire);
                sb.append(", taskName=").append(taskName);
                sb.append("}");
                return sb.toString();
            }
        }
    
    }

    参考自《java并发编程之美》

  • 相关阅读:
    Google Optimize 安装使用教程
    PostgreSQL drop database 显示会话没有关闭 [已解决]
    c#之线程同步--轻量级同步 Interlocked
    CentOS7 'Username' is not in the sudoers file. This incident will be reported
    Mac 如何删除应用、软件
    Oracle的存储过程基本写法
    我的一个PLSQL【我】 循环嵌套、游标使用、变量定义、查询插入表、批量提交事务、字符串截取、动态sql拼接执行
    Oracle定义常量和变量
    ORACLE中%TYPE和%ROWTYPE的使用
    pls_integer类型
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12245607.html
Copyright © 2020-2023  润新知