BlockingQueue——接口规范
ArrayBlockingQueue
加入队列和取出队列共用一把lock锁,高并发效率比较低;内部维护的是一个数组,每次自然putIndex自动+1;取出元素,putIndex-1并拿出相应元素;
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(100);
arrayBlockingQueue.put("11111");
arrayBlockingQueue.add("22222");
String take1 = arrayBlockingQueue.take();
String take2 = arrayBlockingQueue.take();
System.out.println(take1);
System.out.println(take2);
11111
22222
共用同一把锁源码剖析:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();//利用notFull条件队列上锁
enqueue(e);
} finally {
lock.unlock();
}
}
内置数据存储剖析:
/*
数组尾部拿出元素
*/
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)//到达尾部后重置index
putIndex = 0;
count++;
notEmpty.signal();
}
/*
数组头部拿出元素
*/
private E dequeue() {
final Object[] items = this.items;
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)//达到index后重置
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
ps:看完以上源码,就知道了并没有什么元素满了就自动清空,
而是利用putIndex不断覆盖,用takeIndex不断向后,然后由重置开始拿;
临界资源都是依靠条件队列实现控制的
条件队列剖析(显然为了控制数组元素满了后,如何阻塞线程直到拿出了一个元素随机通知一个线程可以放置元素了):
以下是阻塞的条件,count元素数量等于length时
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
解除阻塞时,随机通知一个线程解锁
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
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.signal();//利用notFull条件队列解锁
return x;
}
LinkedBolckingQueue
查找的时候,由于需要遍历所以效率比较低;高并发情况下,拿出数据和插入的数据是两把锁,互不影响;且只需要维护一个头部尾部、尾部指针就可以直接更新这个指针达到快速尾插,头拿的效果;
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
删除元素的时候调用读锁+写锁,避免该元素某个元素还没修改被拿出去并变成连接导致指针错误
public boolean remove(Object o) {
if (o == null) return false;
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();
}
}
void fullyLock() {
putLock.lock();
takeLock.lock();
}
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
链表显然是效率非高的,只需要维护两个指针且读写分离锁的设计让高并发非常高;
SynchronousQueue
内置两种实现,一共用栈数据结构实现的非公平模式;一种用链表实现的公平模式;在这个SynchronousQueue中,生产者线程put之后,该消息必须被另外一个线程消费了;生产者线程才能被重新唤醒,以此实现了协程设计,但是利用这个队列在被阻塞之前会进入一段自旋,而不会立即进入阻塞;
核心要点
可以指定锁的公平性
队列内部不会存储元素,所以尽量避免使用add,offer此类立即返回的方法,除非有特殊需求
SynchronousQueue synchronousQueue = new SynchronousQueue();
new Thread(){
public void run(){
while (true){
try {
System.out.println("开始轮询等待产品");
Thread.sleep(2000);
synchronousQueue.take();
System.out.println("拿到了产品,开始消费");
}catch (Exception e){
e.printStackTrace();
}
}
}
}.start();
synchronousQueue.put("11111");
System.out.println("生产的消息消费成功");
源码分析(敬请期待)
PriorityBlockingQueue
插入队列的对象必须了Comparable接口,然后插入的时候安装Comparable接口排序,但是拿出来仍然是头尾部
DelayQueue
插入队列的对象必须实现了Delayed接口。以说明消息被延迟消费的时间,然后消息在被消费时会查看该消息延时是否满足条件了才决定消费