概述
- 阻塞队列的好处:当队列满时,不需要用代码失去实现禁止往队列中存储数据,当队列不为空时,也会禁止消费者来消费,给使用这提供了方便性与安全性。
- 阻塞队列的种类:
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
- PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列。
- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
- 几个方法:
- 增加(只能在队尾增加元素):add、put、offer
- 队首(只能在队头移除元素):peek、poll、take
- 移除:remove(在队头移除元素),remove(object o)(移除队列中的某个元素)
源码
-
本节主要介绍List类型的阻塞队列
-
队列和集合->Array结构
- 相同点:都是线性结构、底层实现Object数组
- 区别:
- 集合各个位置都可以添加和删除元素,队列只能在一端进行插入一端进行删除,是先进先出的线性结构,由于队列是先进先出,插入在队尾,移除在队列的头部。
- 集合中移除元素有可能会导致一部分位置的元素index发生变化,而队列中在某一端移除和增加元素不会影响其他的位置的index,也就是说集合中移除元素有可能对数据进行移动。例如List[1,2,3],移除元素2后的结果List[1,3],元素3的index发生了变化。Deque[1,2,3],移除一个(只能从1的一段移除元素)Deque[null,2,3],元素中的index并没有发生变化。
- 集合和队列移除某个元素的实现原理是大致相同的,remove(object),都需要将数组中元素进行移动。
-
实现原理思路:存放元素的结构是Object数组,在队列的实现数组中,队列是一个循环的数组,插入数据或者移除数据时的索引和数组的长度的数值相同时,需要将索引重置为初始值。
- 概述:一端插入一端删除,在有限的空间内,要知道下一次插入数据的位置和移除数据的位置,所以在队列的实现中需要记录插入元素的位置和移除元素的位置。队列还存在空和满的情况,所以还需要知道队列中存在元素的个数。
- 增加实现:
- 将当前元素插入队尾,队尾可以是数组当中的任何位置,
- 从队列中获取元素的位置正好和插入位置相反,从队列中移除元素在队列的头部,增加元素在队列的尾部,所以队列是一个循环的结构,当数组满时,需要等待队列中的元素被消费,消费的元素只能是队列的头部,所以当队列满时需要将插入位置的索引置为队列的头部
- 移除的实现:移除头索引的数据,如果队列为空,则等待,如果移除了数据,队列的数量减一
- remove(Object):在队列中查找该元素,如果找到则移除该元素,同时将后面的元素移动,如果没有找到,则不删除。
-
全局变量
final nal Object[] items;
int takeIndex;//记录队头元素的索引 头索引
int putIndex;// 记录队列尾部的索引 尾索引
int count;
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts 主要用于控制生产者,当队列满时将生产者置于阻塞的状态,
*当队列不为空时,通知生产者生产产品,生产者处于(需要锁+CPU可以将生产的产品存入队列)
*/
private final Condition notFull;
transient Itrs itrs = null; //迭代器,阻塞队列实现的迭代器,初始化时迭代器时,会将队列中的所有数据复制到迭代器中
- take pull方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//lock是可端锁,当线程A请求take方法没有获得锁时,此时线程A处于阻塞的状态,可以被中断
try {
while (count == 0)//队列中没有元素,将消费者线程置于阻塞
notEmpty.await();
return dequeue(); //获取元素
} finally {
lock.unlock();
}
}
public E poll() {//remove()调用该方法-> remove()和remove(Object o不同)
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;//将队列中该位置的元素置为null
if (++takeIndex == items.length)//因为队列是循环结构,当头索引在队列的最后一个位置时,需要将头索引指向数组的第一个位置
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();//唤醒处于阻塞中的生产者线程
return x;
}
- offer put
public boolean offer(E e) {//add调用该方法
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} 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();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;//增加元素
if (++putIndex == items.length)//尾索引处于数组的最后一个位置时,需要将尾索引置为第一个位置
putIndex = 0;
count++;
notEmpty.signal();//唤醒消费者线程
}
- remove(object)
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {//数组中存在元素
final int putIndex = this.putIndex;/
int i = takeIndex;
//从头索引开始到尾索引查找是否存在要移除的元素,如果找到,移除该位置的元素
do {
if (o.equals(items[i])) { //队列中的该元素和移除的目标元素相同,
removeAt(i);//移除该位置的元素
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
void removeAt(final int removeIndex) {
final Object[] items = this.items;
if (removeIndex == takeIndex) { //移除的元素正好的队尾元素
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
//循环查找索引的位置,同时移动元素,存在数据的范围是takeIndex到putIndex
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)//index到数组的最后一个位置
next = 0;
if (next != putIndex) {//移动元素
items[i] = items[next];
i = next;
} else {//找到元素删除
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
问题
- 阻塞队列为什么要加锁实现增加和移除操作?
- 会出现线程安全问题,同一个位置同时存在多个生产者生产产品时会出现替换操作,同一个位置多个消费时会出现重复消费的问题
- 通过ArrayDeque和wait、notify、notifyAll自定义实现的阻塞队列时,如果存在多个生产者(p1,p2,p3)和多个消费者(c1,c2,c3),加锁对象是该ArrayDeque队列,某个时刻p1生产了产品后,当前队列满了,此时调用了notify方法会唤醒那个线程?如果唤醒生产者,则生产者消费产品,如果唤醒消费者呢?如果消费者数量较多,notify会不会唤醒很多消费者而没有唤醒一个消费者呢?notify 并没有指定应该唤醒那种线程。
- notify唤醒的线程是处于阻塞中的所有线程,而不是指哪一个,也不能指定哪一个。
- 阻塞队列实现了迭代器,在移除元素时对迭代器中的元素进行操作,打算找个时间把List和ArrayBlockingQueue的迭代器实现总结一下。
if (itrs != null) itrs.removedAt(removeIndex);
这一段就是对迭代器中的元素进行操作。
使用
public class Test {
private int queueSize = 10;
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize);
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume() {
while(true){
try {
queue.take();
System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
while(true){
try {
queue.put(1);
System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
候补
wait、notify、await、signal 的思考:
- wait,await是使获得锁的线程处于阻塞状态,同时释放锁,释放cpu资源,之后的线程需要被唤醒才能进行使用。
- notify,signal是唤醒等待线程中的一个线程,某一个线程,其实所有的等待线程形成了要给队列,所以唤醒线程从队列中获取的,也就是有序的,公平锁和非公平锁主要是在形成队列时候的区别
- 仔细观察下阻塞队列存在两个锁,队列一端进,一端出,所以分别在进出的时候有两个锁,这两个锁的目的就是为了在唤醒线程时候有所指向,如果进队列时发现线程满了,无法进入,那么唤醒的出队列的线程对队列中的元素进行消费,反之出队列通知入队列生产元素~
参考
https://www.cnblogs.com/dolphin0520/p/3932906.html
https://blog.csdn.net/u013991521/article/details/53068713