目录
在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪一种实现都继承自Queue。
ConcurrentLinkedQueue
ConcurrentLinkedQueue是一种适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。通常,ConcurrentLinkedQueue性能好于BlockingQueue,它是一个基于链接节点的无界线程安全队列。该队列遵循先进先出的原则,但不允许添加null的元素。
ConcurrentLinkedQueue重要方法:
- add()和offer()都是加入元素的方法,在ConcurrentLinkedQueue中,这两个方法没有任何区别。
- poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。
实例代码如下:
public class UseQueue {
public static void main(String[] args) throws Exception {
//高性能无阻塞无界队列:ConcurrentLinkedQueue
ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();
q.offer("a");
q.offer("b");
q.offer("c");
q.offer("d");
q.add("e");
System.out.println(q.poll()); //a 从头部取出元素,并从队列里删除
System.out.println(q.size()); //4
System.out.println(q.peek()); //b
System.out.println(q.size()); //4
}
}
输出结果如下:
a
4
b
4
BlockingQueue接口
阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。支持阻塞的插入方法意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满;支持阻塞的移除方法意思是在队列为空时,获取元素的线程会等待队列变为非空。
阻塞队列常常用于生产者和消费者的场景,生产者向队列里添加元素的线程,消费者从队列里获取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。在阻塞队列不可用时,这两个附加操作提供了4种处理方式。如下表所示:
- 抛出异常:当队列满时,如果再往队列里面插入元素,会抛出IllegalStateException异常。当队列为空时,从队列里获取元素会抛出NoSuchElementException异常。
- 返回特殊值:当往队列插入元素时,会返回元素是否插入成功,成功返回true。是移除方法,则是从队列里取出一个元素,如果没有则返回null。
- 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。
- 超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。
如果使用无界阻塞队列,队列不可能会出现满的情况,所以使用put或offer方法永远不会被阻塞,而且使用offer方法时,该方法永远返回true。
BlockingQueue主要有以下的几种实现:
- ArrayBlockingQueue:基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长的数组,以便缓存队列中的数据对象,其内部没有实现读写分离,也就意味着生产和消费不能完全并行。长度需要在构造时指定,可以指定是先进先出还是先进后出,也叫有界队列,在很多场合非常适合使用。
- LinkedBlockingQueue:基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行。它是一个无界队列。
- SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并消费。因此生产者线程对其的插入操作put,必须等待消费者的移除操作take,反过来也一样。数据并不会缓存到SynchronousQueue。
- PriorityBlockingQueue:基于优先级的阻塞队列,优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口。这里需要注意的是,PriorityBlockingQueue并不会在元素插入的时候,对元素进行优先级排序,而是每次执行移出操作的时候,将下次要被移出的元素做优先级排序,放到队列的头部。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,它也是一个无界队列。
- DelayQueue:带有延迟时间的Queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。
BlockingQueue的核心方法如下:
放入数据:
- offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
- offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
- put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
获取数据:
- poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
- poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
- drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
ArrayBlockingQueue的实例代码如下:
public class UseQueue {
public static void main(String[] args) throws Exception {
ArrayBlockingQueue<String> array = new ArrayBlockingQueue<String>(5);
array.put("a");
array.put("b");
array.add("c");
array.add("d");
array.add("e");
System.out.println(array.offer("a", 3, TimeUnit.SECONDS));
array.add("f");
}
}
输出结果为:
false
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
at com.bjsxt.base.coll013.UseQueue.main(UseQueue.java:23)
从上面的代码中,ArrayBlockingQueue中有put、add和offer三个方法都是往队列中添加元素。当队列元素达到5个元素的时候,使用array.offer("a", 3, TimeUnit.SECONDS)继续添加元素,当3秒过后仍然没有将元素添加进去,就会报出false,而使用add添加元素的时候会报出异常:Queue full。
LinkedBlockingQueue的实例代码如下:
public class UseQueue {
public static void main(String[] args) throws Exception {
//阻塞队列
LinkedBlockingQueue<String> q = new LinkedBlockingQueue<String>();
q.offer("a");
q.offer("b");
q.offer("c");
q.offer("d");
q.offer("e");
q.add("f");
System.out.println("LinkedBlockingQueue元素的个数为:" + q.size());
System.out.print("LinkedBlockingQueue中的元素为:");
for (Iterator iterator = q.iterator(); iterator.hasNext(); ) {
String string = (String) iterator.next();
System.out.print(string + " ");
}
System.out.println();
List<String> list = new ArrayList<String>();
System.out.println("从阻塞队列一次性获取元素的个数:" + q.drainTo(list, 3));
System.out.println("获取的元素的大小为:" + list.size());
System.out.print("获取到的元素为:");
for (String string : list) {
System.out.print(string + " ");
}
System.out.println();
System.out.println("LinkedBlockingQueue元素为:" + q);
}
}
输出结果如下:
LinkedBlockingQueue元素的个数为:6
LinkedBlockingQueue中的元素为:a b c d e f
从阻塞队列一次性获取元素的个数:3
获取的元素的大小为:3
获取到的元素为:a b c
LinkedBlockingQueue元素为:[d, e, f]
SynchronousQueue的实例代码如下:
public class UseQueue {
public static void main(String[] args) throws Exception {
//没有容量
final SynchronousQueue<String> q = new SynchronousQueue<String>();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
q.put("Hello World");
q.put("Hello World");
System.out.println(q.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(q.take());
System.out.println(q.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
}
}
输出的结果为:
Hello World
0
Hello World
PriorityBlockingQueue的实现实例代码:
我们先定义一个比较对象Task
public class Task implements Comparable<Task>{
private int id ;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Task task) {
return this.id > task.id ? 1 : (this.id < task.id ? -1 : 0);
}
public String toString(){
return this.id + "," + this.name;
}
}
定义优先级队列的实现类:
public class UsePriorityBlockingQueue {
public static void main(String[] args) throws Exception {
PriorityBlockingQueue<Task> q = new PriorityBlockingQueue<Task>();
Task t1 = new Task();
t1.setId(1);
t1.setName("id为1");
Task t2 = new Task();
t2.setId(2);
t2.setName("id为2");
Task t3 = new Task();
t3.setId(3);
t3.setName("id为3");
q.add(t3);
q.add(t2);
q.add(t1);
// 1 3 4
System.out.println("容器:" + q);
System.out.println(q.take().getId());
System.out.println("容器:" + q);
}
}
输出的结果为:
容器:[1,id为1, 3,id为3, 2,id为2]
1
容器:[2,id为2, 3,id为3]
DelayQueue的实例代码如下:
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class Wangmin implements Delayed {
private String name;
//身份证
private String id;
//截止时间
private long endTime;
//定义时间工具类
private TimeUnit timeUnit = TimeUnit.SECONDS;
public Wangmin(String name,String id,long endTime){
this.name=name;
this.id=id;
this.endTime = endTime;
}
public String getName(){
return this.name;
}
public String getId(){
return this.id;
}
/**
* 用来判断是否到了截止时间
*/
@Override
public long getDelay(TimeUnit unit) {
return endTime - System.currentTimeMillis();
}
/**
* 相互批较排序用
*/
@Override
public int compareTo(Delayed delayed) {
Wangmin w = (Wangmin)delayed;
return this.getDelay(this.timeUnit) - w.getDelay(this.timeUnit) > 0 ? 1:0;
}
}
import java.util.concurrent.DelayQueue;
public class WangBa implements Runnable {
private DelayQueue<Wangmin> queue = new DelayQueue<Wangmin>();
public boolean yinye = true;
public void shangji(String name, String id, int money) {
Wangmin man = new Wangmin(name, id, 1000 * money + System.currentTimeMillis());
System.out.println("网名" + man.getName() + " 身份证" + man.getId() + "交钱" + money + "块,开始上机...");
this.queue.add(man);
}
public void xiaji(Wangmin man) {
System.out.println("网名" + man.getName() + " 身份证" + man.getId() + "时间到下机...");
}
@Override
public void run() {
while (yinye) {
try {
Wangmin man = queue.take();
xiaji(man);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]) {
try {
System.out.println("网吧开始营业");
WangBa siyu = new WangBa();
Thread shangwang = new Thread(siyu);
shangwang.start();
siyu.shangji("路人甲", "123", 1);
siyu.shangji("路人乙", "234", 10);
siyu.shangji("路人丙", "345", 5);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行的结果如下:
网吧开始营业
网名路人甲 身份证123交钱1块,开始上机...
网名路人乙 身份证234交钱10块,开始上机...
网名路人丙 身份证345交钱5块,开始上机...
网名路人甲 身份证123时间到下机...
网名路人丙 身份证345时间到下机...
网名路人乙 身份证234时间到下机...