一、并发队列的有界和无界
并发队列:实际上就是在并发场景下使用的队列。
有/无界概念:有界,就是规定了队列的大小,比如初始值给定位16。无界不是真的无界,是整形的最大值,这个值是达不到的(因为内存不够),所以通常称为无界
有界队列
常见的有界队列
- ArrayBlockingQueue:基于数组实现的阻塞队列
- LinkedBlockingQueue :基于链表实现的阻塞队列,该有界队列不设置大小时就是Integer.MAX_VALUE
- SynchronousQueue :内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时使用CAS代替了原来的锁逻辑
有界队列的共性
- put 、take 操作都是阻塞的
- offer、poll 操作非阻塞的
- offer操作时,若队列满了会返回false,不会阻塞
- poll操作时,若队列为空会返回null,不会阻塞
- 并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用CPU,在有些场景也是需要阻塞的,put、take操作存在必有其存在的必然性
ArrayBlockingQueue 与 LinkedBlockingQueue 对比
- ArrayBlockingQueue 实现简单,表现稳定,添加和删除操作使用同一个锁,通常性能不如后者
- LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
无界队列
常见的无界队列
- ConcurrentLinkedQueue:无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常;
- PriorityBlockingQueue :具有优先级的阻塞队列;
- DelayedQueue :延时队列,使用场景:
- 缓存 :清掉缓存中超时的缓存数据
- 任务超时处理
- 内部实现其实是采用带时间的优先队列,可重入锁,优化阻塞通知的线程元素leader
- LinkedTransferQueue:简单的说也是进行线程间数据交换的利器
无界队列的共性
- put操作永远都不会阻塞,空间限制来源于系统资源的限制
- 底层都使用CAS无锁编程
二、并发队列的阻塞式与非阻塞式的区别
阻塞式队列
- 入列(存)
- 出列(取) :如果获取队列为空的情况下,这时候也会进行等待(阻塞)。这时候队列中没有队列,消费者无法消费队列,只有生产者往对队列中存放队列之后,消费者才可以进行消费。
非阻塞队列
- 入列(存)、出列(取):均直接返回结果,不会阻塞去等待。
三、非阻塞队列详解
ConcurrentLinkedQueue
定义:一个基于链接节点的无界线程安全队列。
特点:
- 此队列按照 FIFO(先进先出)原则对元素进行排序。
- 队列的头部 是队列中时间最长的元素,队列的尾部 是队列中时间最短的元素。
- 当我们获取一个元素时,它会返回队列头部的元素。
常用方法:
- add(E e) :将指定元素插入此队列的尾部,返回值为Boolean,源码内部是直接调用的offer()方法。
- contains(Object o) :如果此队列包含指定元素,则返回 true。
- isEmpty() :如果此队列不包含任何元素,则返回 true。
- iterator():返回在此队列元素上以恰当顺序进行迭代的迭代器,返回值为 Iterator。
- offer(E e):将指定元素插入此队列的尾部,返回值为 boolean。
- peek():获取但不移除此队列的头;如果此队列为空,则返回 null。
- poll() :获取并移除此队列的头,如果此队列为空,则返回 null。
- remove(Object o) :从队列中移除指定元素的单个实例(如果存在),返回值为 boolean。
- size() :返回此队列中的元素数量。
- toArray():返回以恰当顺序包含此队列所有元素的数组。
举个例子:
public class ConcurrentLinkedQueueTest {
public static void main(String[] args) {
/**
* 无参构造方法
* ConcurrentLinkedQueue(): 创建一个最初为空的 ConcurrentLinkedQueue
*/
ConcurrentLinkedQueue<Integer> concurrentLinkedQueue = new ConcurrentLinkedQueue();
/**
* 1、add(E e) :将指定元素插入此队列的尾部,返回值为Boolean。
*/
System.out.println("=====1、从队列尾部添加5=====");
Boolean addBoolean = concurrentLinkedQueue.add(5);
System.out.println("是否添加到队列尾部成功: " + addBoolean);
System.out.println();
/**
* 2、contains(Object o) :如果此队列包含指定元素,则返回 true。
*/
System.out.println("=====2、查看队列中是否包含5=====");
Boolean containsBoolean = concurrentLinkedQueue.contains(5);
System.out.println("是否包含5:" + containsBoolean);
System.out.println();
/**
* 3、isEmpty() :如果此队列不包含任何元素,则返回 true
*/
System.out.println("=====3、判断队列是否为空=====");
Boolean isEmptyBoolean = concurrentLinkedQueue.isEmpty();
System.out.println("concurrentLinkedQueue是否为空:" + isEmptyBoolean);
System.out.println();
/**
* 4、iterator() :返回在此队列元素上以恰当顺序进行迭代的迭代器,返回值为 Iterator<E> 。
*/
concurrentLinkedQueue.add(6);
concurrentLinkedQueue.add(7);
concurrentLinkedQueue.add(8);
System.out.println("=====4、迭代队列元素=====");
Iterator<Integer> iterator = concurrentLinkedQueue.iterator();
System.out.println("iterator的结果:");
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
System.out.println();
/**
* 5、offer(E e) :将指定元素插入此队列的尾部,返回值为boolean。
*/
System.out.println("=====5、offer()方法插入元素=====");
Boolean offerBoolean = concurrentLinkedQueue.offer(9);
System.out.println("是否插入队列尾部成功:" + offerBoolean);
System.out.println();
/**
* 6、peek() :获取但不移除此队列的头;如果此队列为空,则返回 null。
*/
System.out.println("=====6、peek()方法获取元素【元素不出队列】=====");
Integer peekResult = concurrentLinkedQueue.peek();
System.out.println("队列的第一个信息:" + peekResult);
System.out.println();
/**
* 7、poll() :获取并移除此队列的头,如果此队列为空,则返回 null。
*/
System.out.println("=====7、poll()方法获取元素【元素出列】=====");
Integer pollResult = concurrentLinkedQueue.poll();
System.out.println("队列的第一个信息【此时元素已经移出队列】:" + pollResult);
System.out.println();
/**
* 8、remove(Object o) :从队列中移除指定元素的单个实例(如果存在),返回值为Boolean。
*/
System.out.println("=====8、remove()方法移除队列中的元素=====");
Boolean removeBoolean = concurrentLinkedQueue.remove(9);
System.out.println("是否移除9成功?" + removeBoolean);
System.out.println();
/**
* 9、size():返回此队列中的元素数量
*/
System.out.println("=====9、输出队列中的元素数量=====");
Integer size = concurrentLinkedQueue.size();
System.out.println("队列的元素数量:" + size);
}
}
四、阻塞队列详解
阻塞队列分类:
- DelayQueue :基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
- SynchronousQueue:实际上不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列【用作配对】。
- ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列元素最优先能够访问。
- LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
- PriorityBlockingQueue :PriorityBlockingQueue会按照元素的优先级对元素进行排序,按照优先级顺序出队【优先级队列】,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志)。
常用方法:
- add(E e) :将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常。
- remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常。
- offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false
- offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
- put(Object obj):把obj加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续。
- poll() :移除并获取队首元素,若成功,则返回队首元素;否则返回null
- poll(long time) :取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
- poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则超时还没有数据可取,返回失败。
- take() :取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入。
- drainTo() :一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
- peek() :获取队首元素,若成功,则返回队首元素;否则返回null。
举个例子:
/**
* 阻塞队列测试【以数组阻塞队列为例(公平)】
*/
public class BlockQueueTest {
private int queueSize = 10;//数组长的为10
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize, false);//数组阻塞队列【公平队列】
public static void main(String[] args) {
BlockQueueTest test = new BlockQueueTest();
Producer producer = test.new Producer();//生产者
Consumer consumer = test.new Consumer();//消费者
producer.start();
consumer.start();
}
/**
* 消费者线程
*/
public class Consumer extends Thread {
@Override
public void run() {
consume();
}
//消费方法
private void consume() {
int i = 5;
//一直循环去队列里取元素
while (i > 0) {
try {
Integer take = queue.take();
System.out.println("从队列取走元素:" + take + " 队列剩余" + queue.size() + "个元素");
i--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 生产者线程
*/
public class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
int i = 5;
//一直循环去队列里插入元素
while (i > 0) {
try {
boolean offer = queue.offer(i);
System.out.println("向队列插入一个元素成功? " + offer + " 队列剩余空间:" + (queueSize - queue.size()));
i--;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}