java并发队列之优先级队列PriorityBlockingQueue(四)

java并发队列之优先级队列PriorityBlockingQueue(四)

PriorityBlockingQueue是一个数组实现的带优先级无阻塞队列并发安全队列.

java并发队列之优先级队列PriorityBlockingQueue(四)_第1张图片

实战

import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) {
		 //初始化队列大小为10
        final BlockingQueue<String> deque = new PriorityBlockingQueue<>(10);

        Runnable producerRunnable = new Runnable() {
            int i = 0;

            public void run() {
                while (true) {
                    i++;
                    try {
                        log.info("我生产了一个===" + i);
                        deque.put(i + "dddd");
                        //这里估计把时间改小,你会发现队列里面的值一直在增加,超过10个元素之后任然一直再加.
                        //这就是无界队列.当队列满10个之后,会自动扩充队列大小.
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Runnable customerRunnable = new Runnable() {
            public void run() {
                while (true) {
                    try {
                        log.info("我消费了一个===" + deque.take());
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread1 = new Thread(producerRunnable);
        thread1.start();

        Thread thread2 = new Thread(customerRunnable);
        thread2.start();

    }
}

PriorityBlockingQueue特性

  • PriorityBlockingQueue内部有一个数组queue.
  • PriorityBlockingQueue内部是一个数组,但是其实数据结构是使用数组实现的一个最小堆.压入队列时需要计算最小堆,弹出队列时需要重新调整根节点.
  • 带优先级的队列(带排序功能).
  • 无界队列,默认队列大小为11,当队列满了之后会自动扩容(和ArrayList类似的扩容数组)
  • take队列为空时阻塞.但是PriorityBlockingQueue队列是无界的,put方法不存在队列满的时候阻塞的情况,所以put方法是不阻塞的.可以说PriorityBlockingQueue是一个半阻塞的队列.
  • 和ArrayBlockingQueue类似,是独占锁来控制的, 就是说多线程访问时只能有一个线程可以进行入队或出队操作.
  • PriorityBlockingQueue虽然是无界的,但是最大长度只能为Integer.MAX_VALUE - 8.并不是说无界了就可以任意长度.毕竟它是通过数组实现的,数组的最大值只能为Integer.MAX_VALUE.

源码分析

无阻塞put方法

//不需要阻塞,可以看到直接调用offer方法了
public void put(E e) {
        offer(e); // never need to block
    }
    
public boolean add(E e) {
        return offer(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)
                //真正的插入操作
                siftUpComparable(n, e, array);
            else
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }

可以看到整个方法中没有设置await方法.所以在put队列时是无阻塞的.

put方法扩容队列

private void tryGrow(Object[] array, int oldCap) {
 		  //必须释放主锁
        lock.unlock(); // must release and then re-acquire main lock
        Object[] newArray = null;
        //使用cas,如果CAS成功则开始扩容
        if (allocationSpinLock == 0 &&
            UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                     0, 1)) {
            try {
                int newCap = oldCap + ((oldCap < 64) ?
                                       (oldCap + 2) : // grow faster if small
                                       (oldCap >> 1));
                if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                    int minCap = oldCap + 1;
                    if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                        throw new OutOfMemoryError();
                    newCap = MAX_ARRAY_SIZE;
                }
                if (newCap > oldCap && queue == array)
                    newArray = new Object[newCap];
            } finally {
                allocationSpinLock = 0;
            }
        }
        //第一个线程CAS成功后,第二个线程会进入这段代码, 然后第二个线程让出CPU ,尽量让第一个线程
获取锁,但是这得不到保证。 yield只是礼貌的让你一下,自己停顿一下让你先行,但是让你之后自己也可以立即执行.所以这个让你先走你不一定就真的能走在我前面.
        if (newArray == null) // 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);
        }
    }

可以看出扩容:队列长度小于64时,每次扩容增加两倍+2个元素;大于64时,每次扩容1.5倍.
扩容时前先解锁,为什么? 为了提高队列的高并发性能. 当队列大的时候扩容是很耗时的一个操作,在扩容时先解锁,让其他线程可以进行读取或继续写入操作.而使用CAS来保证只有一个线程能扩容成功,此举一举多得,即提高了读写性能又保证了多线程安全性.

siftUpComparable插入操作

//默认排序插入操作
private static  void siftUpComparable(int k, T x, Object[] array) {
        Comparable key = (Comparable) 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;
    }

这里其实就是一个简单排序算法.
步骤解析:第一次压入元素为5,队列初始化长度为10.k=0,x=5 ,array[10];
k=0所以,array[0]=5;没毛病.
第二次压入元素为2,队列长度没有扩容所以还是10.k=1,x=2,array[10];
k=1,进入while循环.
parant=0;
Object e = array[0]= 5;
2与5比较>=0吗? false
array[1]=5;
k=0;
出了while循环之后array[0]=key=2; 队列里面就变成了2,5.自带排序功能.

take阻塞方法

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        E result;
        try {
            //队列等于空时阻塞
            while ( (result = dequeue()) == null)
                notEmpty.await();
        } finally {
            lock.unlock();
        }
        return result;
    }
    
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;
        }
    }

阻塞方法可以看出其实很简单,就是队列空的时候阻塞一下就OK了.

总结: PriorityBlockingQueue其实是一个无界阻塞队列.但只是读取时为阻塞的,在写入时是无阻塞的.使用独占锁来保证读取和写入的多线程安全性,读写同时只能有一个线程操作,但是在扩容时使用CAS锁来进行扩容提高读写性能. 使用最小堆算法来保证队列的有序性,从而达到优先级队列.

你可能感兴趣的:(Java多线程全面解刨)