并发包中的并发队列

一、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并发编程之美》

posted on 2020-01-31 15:50  FFStayF  阅读(285)  评论(0编辑  收藏  举报