相关文章:
Java 集合框架分析:Set
http://blog.csdn.net/youyou1543724847/article/details/52733723
Java 集合框架分析:LinkedList
http://blog.csdn.net/youyou1543724847/article/details/52734935
Java 集合框架分析:DelayQueue
http://blog.csdn.net/youyou1543724847/article/details/52176504
Java 集合框架分析:ArrayBlockingQueue
http://blog.csdn.net/youyou1543724847/article/details/52174308
Java 集合框架分析:ArrayDeque
http://blog.csdn.net/youyou1543724847/article/details/52170026
Java 集合框架分析:PriorityBlockingQueue
http://blog.csdn.net/youyou1543724847/article/details/52166985
Java 集合框架分析:JAVA Queue源码分析
http://blog.csdn.net/youyou1543724847/article/details/52164895
Java 集合框架分析:关于Set,Map集合中元素判等的方式
http://blog.csdn.net/youyou1543724847/article/details/52733766
Java 集合框架分析:ConcurrentModificationException
http://blog.csdn.net/youyou1543724847/article/details/52733780
Java 集合框架分析:线程安全的集合
http://blog.csdn.net/youyou1543724847/article/details/52734876
Java 集合框架分析:JAVA集合中的一些边边角角的知识
http://blog.csdn.net/youyou1543724847/article/details/52734918
哈哈,终于有了第二篇博客了,终于知道编辑一个博客需要注意什么了,希望坚持下去,每天看点小源码!
目录
1.简述PriorityBlockingQueue
2.主要方法及实现
3.使用过程中需要注意的地方
4.和其他的相关容器的比较
5.总结
简述PriorityBlockingQueue
特点:
1.属于并发安全的集合。(什么是并发安全的集合:即多线程的情况下,不会出现不确定的状态)。
2.无界的队列。
3.它的blocking表现在取元素时,如果队列为空,则取元素的线程会阻塞。
4.不允许null元素
主要方法及实现
1.主要的成员
同Priorityqueue,使用底层数组保存数据,拥有两把锁,一个可重入锁,一个自旋锁。
private final ReentrantLock lock;
/**
* Condition for blocking when empty
*/
private final Condition notEmpty;
/**
* Spinlock for allocation, acquired via CAS.
*/
private transient volatile int allocationSpinLock;
private transient Object[] queue;
/**
* The number of elements in the priority queue.
*/
private transient int size;
2.插入
步骤:
a.null检测
b.因为priorityblockingQueue是线程安全的,所以,插入删除都是需要加锁的。这里先进行加锁。
c.如果需要扩容,则先扩容。关于扩容操作,一会再说。
d.插入元素,调整顺序(和priorityqueue是一样的)
e.发送信息,激活阻塞的取数据的线程
f.释放锁(必须的,因为不是用的Synchronized,而是用的lock进行控制的)
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;
}
3.扩容操作
扩容发生在你要插入元素时,发现底层数组大小不够,则需要扩容。这里在判断时,已经获取的reentrantlock锁,因为要扩容,说明queue不为空。另一方面,扩容时,需要发生底层数组的重新复制到新数组中,而取数据的线程当前还不会读到新加入的数据(先取你之间加入的,这是happen-before原则,你新数据还没有插入成功,别人是看不到的),所以为了提高并发量,这里需要先释放reentrantlock,让其他的读线程能够进行(写线程还是会阻塞,因为要写,还是要进行扩容,会在获取扩容锁时阻塞)。突然想到一个问题,如果A写入时,发现满了,因此要扩容,所示扩容期间,释放了锁。B poll操作,然后C要写入,但是此时容量是允许的,这不就让C在A之前了么?这个问题是什么情况?
这里进行扩容使用的是CAS锁,当获取锁不成功时,说明有其他线程在扩容,则等待。在成功扩容之后,需要重新获取主锁(即reentrantlock),然后修改queue底层数组引用。
private void tryGrow(Object[] array, int oldCap)
{
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
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;
}
}
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);
}
}
4.删除元素
删除元素有多个版本:
不阻塞的:poll()(如果没有元素可用,则直接返回null)
阻塞的:take()
等待一段时间的:poll(long timeout, TimeUnit unit)
实现都大概相同:
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
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;
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null && nanos > 0)
nanos = notEmpty.awaitNanos(nanos);
} finally {
lock.unlock();
}
return result;
}
使用过程中需要注意的地方
1.priorityblockingqueue在操作队列时,都是共用的一把锁(在扩容时,用到了自旋锁,会释放一段主锁,然后重新获取)
2.peek,offer,poll,size等都是要获取同一把锁的,效率不是很高
3.在序列化时,为了提供效率,会先将数据放入到priorityqueue中,然后一次性加入到阻塞队列中,增加操作效率(不用每次都获取锁)
和其他的相关容器的比较
和priorityqueue基本相同,处理有加锁的操作外。
总结
除非是在多线程中,否则不要使用,几乎所有操作都要竞争同一把锁。