PriorityBlockingQueue是一个数组实现的带优先级无阻塞队列并发安全队列.
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();
}
}
//不需要阻塞,可以看到直接调用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队列时是无阻塞的.
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来保证只有一个线程能扩容成功,此举一举多得,即提高了读写性能又保证了多线程安全性.
//默认排序插入操作
private static 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;
}
这里其实就是一个简单排序算法.
步骤解析:第一次压入元素为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.自带排序功能.
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锁来进行扩容提高读写性能. 使用最小堆算法来保证队列的有序性,从而达到优先级队列.