BlockingQueue作为线程容器,可以为线程同步提供有力的保障,其主要用到的方法包括:
add(E o); //将指定的元素添加到此队列中(如果立即可行),在成功时返回 true,其他情况则抛出 IllegalStateException。
drainTo(Collection<? super E> c); //移除此队列中所有可用的元素,并将它们添加到给定 collection 中。
drainTo(Collection<? super E> c,int maxElements);//最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中
offer(E o); //如果可能的话,将指定元素插入此队列中。
offer(E o, long timeout, TimeUnit unit); //将指定的元素插入此队列中,如果没有可用空间,将等待指定的等待时间(如果有必要)。
poll(long timeout, TimeUnit unit); //检索并移除此队列的头部,如果此队列中没有任何元素,则等待指定等待的时间(如果有必要)。
put(E o); //将指定元素添加到此队列中,如果没有可用空间,将一直等待(如果有必要)。
remainingCapacity(); //返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的元素数量;如果没有内部限制,则返回 Integer.MAX_VALUE。
take(); //检索并移除此队列的头部,如果此队列不存在任何元素,则一直等待。
上述方法中主要用到的是put()和take()方法,也只有这两个方法具有阻塞等待功能,另外BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。
BlockingQueue 可以定义为限定容量的队列,它有一个 remainingCapacity容量值,超出此容量,便无法无阻塞地 put 额外的元素。也可以定义为没有容量限制的队列,没有容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。它实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
BlockingQueue 主要用于实现生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。阻塞队列与Semaphore有很大相似性,但也有很多不同,阻塞队列一般是一方存数据,另一方释放数据,而Semaphore通常是同一方获取和释放信号。下面通过一个例子加以说明:
public class BlockingQueueTest {
public static void main(String[] args) {
final BlockingQueue queue = new ArrayBlockingQueue(3);
for(int i=0;i<2;i++){
new Thread(){
public void run(){
while(true){
try {
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "准备放数据!");
queue.put(1);
System.out.println(Thread.currentThread().getName() + "已经放了数据," + "队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
new Thread(){
public void run(){
while(true){
try {
//将此处的睡眠时间分别改为100和1000,观察运行结果
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "准备取数据!");
queue.take();
System.out.println(Thread.currentThread().getName() + "已经取走数据," +
"队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
上例中定义了一个最多可以存放3个数据的BlockingQueue,并创建了两个用于put()的线程,一个用于take()的线程,这边能够更容易使阻塞队列形成满队列,当队列中的有3个数据的时候,两个put()线程就等待,只有当take()线程取走一个数据时才可以继续往队列中添加数据。运行结果如下(只去部分结果):
Thread-1准备放数据!
Thread-1已经放了数据,队列目前有1个数据
Thread-1准备放数据!
Thread-1已经放了数据,队列目前有2个数据
Thread-0准备放数据!
Thread-0已经放了数据,队列目前有3个数据
Thread-0准备放数据!
Thread-2准备取数据!
Thread-2已经取走数据,队列目前有2个数据
既然阻塞队列可以实现线程之间的等待,那么我们就可以通过两个具有1个空间的阻塞队列可以实现线程的同步,关键代码如下:
BlockingQueue<Integer> queue1 = new ArrayBlockingQueue<Integer>(1);
BlockingQueue<Integer> queue2 = new ArrayBlockingQueue<Integer>(1);
public void sub(int i){
try {
queue1.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequece of " + j + ",loop of " + i);
}
try {
queue2.take();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void main(int i){
try {
queue2.put(1);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for(int j=1;j<=100;j++){
System.out.println("main thread sequece of " + j + ",loop of " + i);
}
try {
queue1.take();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
上例中定义了两个方法,一个sub()和一个main(),两个方法要实现同步,由于定义的两个阻塞队列都是容量为1,所以只要有一个queue1.put(1);那么sub()方法就必须等待,只有当main()方法中queue1.take();以后sub()方法才可以继续进行,main()方法也类似。