Queue
- 队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
- 基本常用方法:
- take:首选。当队列为空时阻塞
- offer:将指定元素插入此队列的尾部。队满时返回false. 可以指定超时时间
- poll:获取并移除此队列的头,如果此队列为空,则返回 null。
- peek:获取但不移除此队列的头;如果此队列为空,则返回 null。
- remove:从队列中移除指定元素的单个实例(如果存在)
- put():首选。队满是阻塞
- 判断队列是否为空.size()方法会遍历整个队列,时间复杂度为O(n),所以最好选用isEmtpy
CocurrentLinkedQueue 双端队列
- 定义:
- 一个基于链接节点的无界线程安全队列。此队列按照 先进先出 原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。当多个线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许使用 null 元素。
public static void main(String[] args) {
Queue<String> strs = new ConcurrentLinkedQueue<>();
for (int i = 0; i < 10; i++) {
strs.offer("a" + i);
}
System.out.println("strs:" + strs);
System.out.println("strs.size()" + strs.size());
System.out.println("strs.poll()" + strs.poll());
System.out.println("strs.size()" + strs.size());
System.out.println("strs.peek()" + strs.peek());
System.out.println("strs.size()" + strs.size());
}
运行结果:
strs:[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
strs.size()10
strs.poll()a0
strs.size()9
strs.peek()a1
strs.size()9
BlockingQueue-LinkedBlockingQueue 阻塞队列
- 定义:
- LinkedBlockingQueue:基于链表的阻塞队列,内部维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。LinkedBlockingQueue采用可重入锁(ReentrantLock)来保证在并发情况下的线程安全。
- 示例代码
static BlockingQueue<String> strs = new LinkedBlockingQueue<>();
static Random r = new Random();
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
//如果满了,就会等待
strs.put("test" + i);
System.out.println(Thread.currentThread().getName() + " put" + i);
TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产线程").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (;;) {
try {
//如果空了,就会等待
System.out.println(Thread.currentThread().getName() + " take -" + strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费线程" + i).start();
}
}
运行结果: 可以调整生产者的速度
生产线程 put0
消费线程0 take -test0
生产线程 put1
消费线程1 take -test1
生产线程 put2
消费线程2 take -test2
生产线程 put3
消费线程0 take -test3
生产线程 put4
消费线程3 take -test4
生产线程 put5
消费线程4 take -test5
生产线程 put6
消费线程1 take -test6
BlockingQueue-ArrayBlockingQueue 阻塞队列
- 定义:
- 基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
- 示例代码:
// 初十指定长度为10
static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);
static Random r = new Random();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
strs.put("a" + i);
System.out.println("strs.put:" + "a" + i);
}
//满了就会等待,程序阻塞 主线程会卡住不往下走.
//strs.put("aaa");
//strs.add("aaa");
//strs.offer("aaa");
boolean flag = strs.offer("aaa", 1, TimeUnit.SECONDS);
System.out.println("新增是否成功:" + flag);
System.out.println(strs);
}
执行结果:
strs.put:a0
strs.put:a1
strs.put:a2
strs.put:a3
strs.put:a4
strs.put:a5
strs.put:a6
strs.put:a7
strs.put:a8
strs.put:a9
Disconnected from the target VM, address: '127.0.0.1:60127', transport: 'socket'
新增是否成功:false
[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
BlockingQueue-DelayQueue 延迟队列
- 定义:
- java延迟队列提供了在指定时间才能获取队列元素的功能,队列头元素是最接近过期的元素。没有过期元素的话,使用poll()方法会返回null值,超时判定是通过**getDelay(TimeUnit.NANOSECONDS)**方法的返回值小于等于0来判断。延时队列不能存放空元素。
- 延时队列实现了Iterator接口,但iterator()遍历顺序不保证是元素的实际存放顺序。
- 示例代码:
-
static BlockingQueue<MyTasks> tasks = new DelayQueue<>();
static Random r = new Random();
static class MyTasks implements Delayed {
String name;
long runningTime;
MyTasks(String name, long rt) {
this.name = name;
this.runningTime = rt;
}
@Override
public int compareTo(Delayed o) {
if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
return -1;
else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS))
return 1;
else
return 0;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public String toString() {
return name + " " + runningTime;
}
}
public static void main(String[] args) throws InterruptedException {
long now = System.currentTimeMillis();
MyTasks t1 = new MyTasks("t1", now + 1000);
MyTasks t2 = new MyTasks("t2", now + 2000);
MyTasks t3 = new MyTasks("t3", now + 1500);
MyTasks t4 = new MyTasks("t4", now + 2500);
MyTasks t5 = new MyTasks("t5", now + 500);
tasks.put(t1);
tasks.put(t2);
tasks.put(t3);
tasks.put(t4);
tasks.put(t5);
System.out.println(tasks);
for(int i=0; i<5; i++) {
System.out.println(tasks.take());
}
}
运行结果:
[t5 1601184443280, t1 1601184443780, t3 1601184444280, t4 1601184445280, t2 1601184444780]
t5 1601184443280
t1 1601184443780
t3 1601184444280
t2 1601184444780
t4 1601184445280
BlockingQueue-SynchronousQueue
- 定义:
- SynchronousQueue作为阻塞队列的时候,对于每一个take的线程会阻塞直到有一个put的线程放入元素为止,反之亦然。在SynchronousQueue内部没有任何存放元素的能力。所以类似peek操作或者迭代器操作也是无效的,元素只能通过put类操作或者take类操作才有效。通常队列的第一个元素是当前第一个等待的线程。如果没有线程阻塞在该队列则poll会返回null。从Collection的视角来看SynchronousQueue表现为一个空的集合。
- SynchronousQueue相似于使用CSP和Ada算法(不知道怎么具体指什么算法),他非常适合做交换的工作,生产者的线程必须与消费者的线程同步以传递某些信息、事件或任务
- SynchronousQueue支持支持生产者和消费者等待的公平性策略。默认情况下,不能保证生产消费的顺序。如果是公平锁的话可以保证当前第一个队首的线程是等待时间最长的线程,这时可以视SynchronousQueue为一个FIFO队列
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> strs = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println("strs 执行了take" + strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//阻塞等待消费者消费
strs.put("aaa");
//strs.put("bbb");
//strs.add("aaa");
System.out.println(strs.size());
}
执行结果:
strs 执行了takeaaa
0
PriorityQueue 优先级队列
- 定义:
- PriorityQueue类在Java1.5中引入。PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。要求使用Java Comparable和Comparator接口给对象排序,并且在排序时会按照优先级处理其中的元素.默认顺序是升序.
- 代码示例:
public static void main(String[] args) {
PriorityQueue<String> q = new PriorityQueue<>();
q.add("4");
q.add("3");
q.add("5");
q.add("9");
q.add("1");
//注意: for i 循环的时候 注意poll会改变数组的长度
for (int i = 0; i < 5; i++) {
System.out.println(q.poll());
}
}
返回结果:
1
3
4
5
9
LinkedTransferQueue 无界阻塞
- 定义:
- LinkedTransferQueue采用一种预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回。我们称这种节点操作为“匹配”方式。
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
new Thread(() -> {
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//transfer 如果存在一个消费者已经等待接收它,则立即传送指定的元素,否则等待直到元素被消费者接收。
strs.transfer("新增元素 ");
}