1.阻塞队列 BlockingQueue
(1)介绍:java.util.concurrent 包里的 BlockingQueue 接口表示一个线程安放入和提取实例的队列。
(2)用法:
通常用法:BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。
图解:
过程:一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。 也就是说,它是有限的。--阻塞队列的对象容纳数是有限的
如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。 --发生阻塞的情况之一,生产对象到了阻塞线程的临界点,直到消费线程拿走对象
负责消费的线程将会一直从该阻塞队列中拿出对象。如果消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。
--发生阻塞的情况二,消费阻塞,队列是空的,消费线程拿不到对象,直到一个生产对象进入队列,阻塞才消失
(3)方法
BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
四组不同的行为方式解释:
1. 抛异常:如果试图的操作无法立即执行,抛一个异常。
2. 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
3. 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4. 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
总结:1.抛出异常:add和remove,element ,特定值:offer,poll,peek 阻塞:put,take 超时:offer poll (加上超时时间)
2.BlockingQuene队列不能插入NULL值,否则会抛出NullPointException异常
3.可以访问到 BlockingQueue 中的所有元素,而不仅仅是开始和结束的元素。--意思是说你可以访问其中的对象元素,基于的封装的方法,但考虑其结构是队列结构,效率不会太高
比如说,你将一个对象放入队列之中以等待处理,但你的应用想要将其取消掉。
那么你可以调用诸如remove(o) 方法来将队列之中的特定对象进行移除。
但是这么干效率并不高(译者注:基于队列的数据结构,获取除开始或结束位置的其他对象的效率不会太高),
因此你尽量不要用这一类的方法,除非你确实不得不那么做。
(4)实现(BlockingQuene 是接口)
介绍:BlockingQueue 是 个 接 口 , 你 需 要 使 用 它 的 实 现 之 一 来 使 用 BlockingQueue 。
java.util.concurrent 具有以下 BlockingQueue 接口的实现(Java 6):
ArrayBlockingQueue
DelayQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
(5)以ArrayBlockingQueue实现举例
步骤:1.分别在两个独立的线程中启动了一个 Producer 和 一个Consumer。
2.Producer 向一个共享的 BlockingQueue 中注入字符串,而 Consumer 则会从中把它们拿出来。
如下:
//具体调用
public class BlockingQueueExample {
public static void main(String[] args) throws Exception {
BlockingQueue queue = new ArrayBlockingQueue(1024);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
new Thread(producer).start();
new Thread(consumer).start();
Thread.sleep(4000);
}
}
//以下是 Producer 类。注意它在每次 put() 调用时是如何休眠一秒钟的。这将导致 Consumer在等待队列中对象的时候发生阻塞。
public class Producer implements Runnable{
protected BlockingQueue queue = null;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
queue.put("1");
Thread.sleep(1000);
queue.put("2");
Thread.sleep(1000);
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//以下是 Consumer 类。它只是把对象从队列中抽取出来,然后将它们打印到 System.out。
public class Consumer implements Runnable{
protected BlockingQueue queue = null;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.数组阻塞队列 ArrayBlockingQueue
介绍:1.ArrayBlockingQueue 类实现了 BlockingQueue 接口。
2.ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。
注:(1)有界也就意味着,它不能够存储无限多数量的元素。
(2)它有一个同一时间能够存储元素数量的上限。
(3) 你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了(译者注:因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改)。
3.ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。
实例化例子:
BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1024); --初始化实例,并设置元素上限
queue.put("1"); --往数组队列存放元素(阻塞型)
String string = queue.take(); --往数据队列拿元素(阻塞型)
3.延迟队列 DelayQueue
介绍:1.DelayQueue 实现了 BlockingQueue 接口。
2.DelayQueue 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口,该接口定义:
public interface Delayed extends Comparable<Delayed< {
public long getDelay(TimeUnit timeUnit);
}
作用:DelayQueue 将会在每个元素的 getDelay() 方法返回的值的时间段之后才释放掉该元素。
如果返回的是 0 或者负值,延迟将被认为过期,该元素将会在 DelayQueue 的下一次 take被调用的时候被释放掉。
参数:传递给 getDelay 方法的 getDelay 实例是一个枚举类型,它表明了将要延迟的时间段。
TimeUnit 枚举将会取以下值:
DAYS,HOURS,MINUTES,SECONDS,MILLISECONDS,MICROSECONDS,NANOSECONDS
备注:正如你所看到的,Delayed 接口也继承了 java.lang.Comparable 接口,这也就意味着 Delayed对象之间可以进行对比。这个可能在对 DelayQueue 队列中的元素进行排序时有用,因此它们可以根据过期时间进行有序释放。
例子:
public class DelayQueueExample {
public static void main(String[] args) {
DelayQueue queue = new DelayQueue();
Delayed element1 = new DelayedElement(); --DelayedElement实现了Delayed接口,DelayedElement需要自己去创建,不在java.util.concurrent
queue.put(element1);
Delayed element2 = queue.take();
}
}
注:DelayedElement 是 我 所 创 建 的 一 个 DelayedElement 接 口 的 实 现 类 , 它 不 在java.util.concurrent 包 里 。 你 需 要 自 行 创 建 你 自 己 的 Delayed 接 口 的 实 现 以 使 用DelayQueue 类。
4.链阻塞队列 LinkedBlockingQueue
介绍:1.LinkedBlockingQueue 类实现了 BlockingQueue 接口。
2.LinkedBlockingQueue 内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。
3.LinkedBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储(栈是后进先出,栈是堆的一个应用)。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。
例子:
BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();
BlockingQueue<String> bounded = new LinkedBlockingQueue<String>(1024);
bounded.put("Value");
String value = bounded.take();
5.具有优先级的阻塞队列PriorityBlockingQueue
介绍:1.PriorityBlockingQueue 类实现了 BlockingQueue 接口。
2.PriorityBlockingQueue 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 一样的排序规则。你无法向这个队列中插入 null 值。
3.所有插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。因此该队列中元素的排序就取决于你自己的 Comparable 实现。 --实现了Comparable接口,所以是有序的阻塞队列
注:(1)PriorityBlockingQueue 对于具有相等优先级(compare() == 0)的元素并不强制任何特定行为。
(2)如果你从一个 PriorityBlockingQueue 获得一个 Iterator 的话,该 Iterator 并不能保证它对元素的遍历是以优先级为序的。
例子:
BlockingQueue queue = new PriorityBlockingQueue();
//String implements java.lang.Comparable
queue.put("Value");
String value = queue.take();
6.同步队列 SynchronousQueue
介绍:1.SynchronousQueue 类实现了 BlockingQueue 接口。
2.SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。
如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。
同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。
总结:据此,把这个类称作一个队列显然是夸大其词了。它更多像是一个汇合点。
7.阻塞双端队列 BlockingDeque(名字有区别的别弄错了:阻塞队列 BlockingQueue)
介绍:(1)java.util.concurrent 包里的 BlockingDeque 接口表示一个线程安放入和提取实例的双端队列。
(2)BlockingDeque 类是一个双端队列,在不能够插入元素时,它将阻塞住试图插入元素的线程;在不能够抽取元素时,它将阻塞住试图抽取的线程。
(3) deque(双端队列) 是 "Double Ended Queue" 的缩写。因此,双端队列是一个你可以从任意一端插入或者抽取元素的队列。
用法: (1) 在线程既是一个队列的生产者又是这个队列的消费者的时候可以使用到 BlockingDeque。
(2) 如果生产者线程需要在队列的两端都可以插入数据,消费者线程需要在队列的两端都可以移除数据,这个时候也可以使用 BlockingDeque。
图解:
过程:一个线程生产元素,并把它们插入到队列的任意一端。如果双端队列已满,插入线程将被阻塞,直到一个移除线程从该队列中移出了一个元素。
如果双端队列为空,移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。
方法:
BlockingDeque 具有 4 组不同的方法用于插入、移除以及对双端队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
四组不同的行为方式解释:
1. 抛异常:如果试图的操作无法立即执行,抛一个异常。
2.特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
3. 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4. 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
总结:(1)从队列一端插入删除 addFirst/removeFirst(抛异常),offerFirst/pollFirst (特定值),putFirst/takeFirst(阻塞),offerFirst/pollFirst+时间参数 (超时) 检查:getFirst,peekFirst
(2)从队列另一端插入删除 addLast/removeLast(抛异常),offerLast/pollLast (特定值),putLast/takeLast(阻塞),offerLast/pollLast+时间参数 (超时) 检查:getLasr,peekLast
BlockingDeque 继承自 BlockingQueue:
介绍:BlockingDeque 接 口 继 承 自 BlockingQueue 接 口 。
这 就 意 味 着 你 可 以 像 使 用 一 个BlockingQueue 那样使用 BlockingDeque。
如果你这么干的话,各种插入方法将会把新元素添加到双端队列的尾端,而移除方法将会把双端队列的首端的元素移除。
正如BlockingQueue 接口的插入和移除方法一样。
图解:
以下是 BlockingDeque 对 BlockingQueue 接口的方法的具体内部实现:
总结:添加到尾端,移出首端(先进后出)
示例:
BlockingDeque<String> deque = new LinkedBlockingDeque<String>();
deque.addFirst("1");
deque.addLast("2");
String two = deque.takeLast();
String one = deque.takeFirst();
8.链阻塞双端队列 LinkedBlockingDeque
介绍:(1)LinkedBlockingDeque 类实现了 BlockingDeque 接口。
(2)deque(双端队列) 是 "Double Ended Queue" 的缩写。因此,双端队列是一个你可以从任意一端插入或者抽取元素的队列。(译者注:唐僧啊,受不了。)
(3)LinkedBlockingDeque 是一个双端队列,在它为空的时候,一个试图从中抽取数据的线程将会阻塞,无论该线程是试图从哪一端抽取数据。
示例:
BlockingDeque<String> deque = new LinkedBlockingDeque<String>();
deque.addFirst("1");
deque.addLast("2");
String two = deque.takeLast();
String one = deque.takeFirst();