解读java.util.queue

过去在没有Queue的情况下如何管理? 

在Java 1.5以前,通常的实现方式是使用java.util.List集合来模仿Queue。Queue的概念通过把对象添加(称为enqueuing的操作) 到List的尾部(即Queue的后部)并通过从List的头部(即Queue的前部)提取对象而从List中移除(称为dequeuing的操作)来模 拟。下面代码显示了你以前可能做法。 


import java.util.Enumeration; 
import java.util.LinkedList; 
import java.util.Vector; 

public class QueueEmulate 

     private LinkedList queueMembers; 
     public Object enqueue(Object element) 
     { 
          queueMembers.add (element); 
          return element; 
     } 

     public Object dequeue() 
     { 
          return queueMembers.removeFirst(); 
     } 


现在我们有了Queue 

Java 1.5在java.util包中新添加了Queue接口,下面代码中使用到它(从sample code的SimpleQueueUsageExample.java截取)。注意,在Java 1.5中,java.util.LinkedList被用来实现java.util.Queue,如同java.util.List接口。也要注意我是如 何显式地声明我的只包含字符串的Queue---使用<String> 泛型(如果需要深入了解泛型,请参阅"J2SE 1.5: Java's Evolution Continues")。这样使我省却了当从Queue中提取它们时不得不进行对象造型的痛苦。 


Queue<String> myQueue = new LinkedList<String>(); 
myQueue.add("Add Me"); 
myQueue.add("Add Me Too"); 
String enqueued = myQueue.remove(); 

你可以看到LinkedList类的add和remove方法被分别用于执行enqueuing和dequeuing操作。实际上没有更好的可用 方法;Queue接口提供了新的offer和poll方法,如下显示(截取自SimpleQueueUsageExamplePreferred): 


Queue<String> myQueue = new LinkedList<String>(); 
boolean offerSuccess; 
// offer method tries to enqueue. 
// A boolean is returned telling you if the 
// attempt to add to the queue was successful or not 
offerSuccess=myQueue.offer("Add Me"); 
offerSuccess=myQueue.offer("Add Me Too"); 
// peek at the head of the queue, but don't grab it 
Object pull = myQueue.peek(); 
String enqueued = null; 
// grab the head of the queue 
if (pull!=null) enqueued = myQueue.poll(); 
System.out.println(enqueued); 

如果你的Queue有固定长度限制(常常是这种情形),使用add方法向Queue中添加内容,将导致抛出一个非检查异常。当你编译 SimpleQueueUsageExample的代码时,编译器将会就此问题发出警告。相反,当新的offer方法试图添加时,如果一切正常则会返回 TRUE,否则,返回FALSE。同样地,如果你试图使用remove方法对一个空Queue操作时,也将导致一个非检查异常。 

你也可以使用poll方法去从Queue中提取元素。如果在Queue中没有元素存在,poll方法将会返回一个null(即不会抛出异常)。在 某些情况下,你可能不想提取头部的元素而只是想看一下。Queue接口提供了一个peek方法来做这样的事情。如果你正在处理一个空Queue,peek 方法返回null。象add-offer和remove-poll关系一样,还有peek-element关系。在Queue为空的情形 下,element方法抛出一个非检查异常。但如果在Queue中有元素,peek方法允许你查看一下第一个元素而无需真的把他从Queue中取出来。 peek方法的用法在SimpleQueueUsageExamplePreferred类中有示例。 


AbstractQueue类 

如你所见,java.util.LinkedList类实现了java.util.Queue接口,同样,AbstractQueue也是这样。 AbstractQueue 类实现了java.util接口的一些方法(因此在它的名字中包含abstract)。而AbstractQueue将重点放在了实现 offer,poll和peek方法上。另外使用一些已经提供的具体实现。 

PriorityQueue类 

在PriorityQueue中,当你添加元素到Queue中时,实现了自动排序。根据你使用的PriorityQueue的不同构造 器,Queue元素的顺序要么基于他们的自然顺序要么通过PriorirtyQueue构造器传入的Comparator来确定。下面的代码示例了 PirorityQueue类的使用方法。在Queue的前边是字符串"Alabama"-由于元素在PriorityQueue中是按自然顺序排列的 (此例中是按字母表顺序)。 


PriorityQueue<String> priorityQueue = new PriorityQueue<String>(); 
priorityQueue.offer("Texas"); 
priorityQueue.offer("Alabama"); 
priorityQueue.offer("California"); 
priorityQueue.offer("Rhode Island"); 
int queueSize = priorityQueue.size(); 
for (int i =0; i< queueSize; i++) 

     System.out.println(priorityQueue.poll()); 


执行结果如下: 

Alabama 
California 
Rhode Island 
Texas 
Queue各项按照自然顺序-字母顺序-来排列。 
如上提到的,你可以创建你自己的Comparator类并提供给PirorityQueue。如此,你可以定义你自己的排序方式。在 PriorityQueueComparatorUsageExample 类中可找到此方式,在其中使用了一个名为State的助手类。如你在下边看到的,在类定义中,State只简单地包含了一个名字和人口。 

private String name; 
private int population; 

public State(String name, int population) 

     super(); 
     this.name = name; 
     this.population = population; 


public String getName() 

     return this.name; 


public int getPopulation() 

     return this.population; 

public String toString() 

     return getName() + " - " + getPopulation(); 


在PriorityQueueComparatorUsageExample中,Queue使用了java.util.Comparator的自定义实现来定义排列顺序(如下)。 


PriorityQueue<State> priorityQueue = 
     new PriorityQueue(6, new Comparator<State>() 

     public int compare(State a, State b) 
     { 
       System.out.println("Comparing Populations"); 
       int populationA = a.getPopulation(); 
       int populationB = b.getPopulation(); 
       if (populationB>populationA) 
            return 1; 
       else if (populationB<populationA) 
            return -1; 
       else 
            return 0; 
     } 

); 

执行PriorityQueueComparatorUsageExample类后,添加到Queue中的State对象将按人口数量排放(从低到高)。 


阻塞Queue 

Queue通常限定于给定大小。迄今为止,通过Queue的实现你已经看到,使用offer或add方法enqueue Queue(并用remove或poll来dequeue Queue)都是假设如果Queue不能提供添加或移除操作,那么你不需要等待程序执行。 java.util.concurrent.BlockingQueue接口实现阻塞。它添加了put和take方法。举一个例子可能更有用。 

使用原来的producer/consumer关系来假定你的producer写一个Queue(更特定是一个BlockingQueue)。你 有一些consumer正从Queue中读取,在一个有序的方式下,哪种方式是你希望看到的。基本上,每个consumer需要等待先于它并获准从 Queue中提取项目的前一个consumer。用程序构建此结构,先生成一个producer线程用于向一个Queue中写数据,然后生成一些 consumer线程从同一Queue中读取数据。注意,线程会阻塞另一线程直到当前线程做完从Queue中提取一项的操作。 

下面的代码展示了类Producer写BlockingQueue的过程。注意run方法中的对象(你有责任实现,因为你继承了Thread)在 等待了随机数量的时间(范围从100到500毫秒)后,被放进了BlockingQueue。放到Queue中的对象只是一些包含消息产生时的时间的字符 串。 

添加对象的实际工作是由如下语句实现的: 
blockingQueue.put("Enqueued at: " + time) 

put方法会抛出InterruptedException,因此,put操作需要被try...catch块包围,用来捕获被抛出的异常(见Listing 1)。 

从producer中提取消息的是Consumer对象,它也继承自Thread对象并因此要实现run方法(见Listing 2)。 

Consumer类在设计上是类似于Producer类的。Consumer类使用take方法去从Queue中取出(即dequeue)消息, 而不是将消息放到BlockingQueue中。如前所述,这需要等待到有什么内容确实存在于Queue中时才发生。如果producer线程停止放置 (即enqueue)对象到Queue中,那么consumer将等待到Queue的项目有效为止。下面所示的TestBlockingQueue类,产 生四个consumer线程,它们从BlockingQueue中尝试提取对象。 

import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.LinkedBlockingQueue; 

public class TestBlockingQueue 

public static void main(String args[]) 

    BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>(); 
    Producer producer = new Producer(blockingQueue, System.out); 
    Consumer consumerA = new Consumer("ConsumerA", blockingQueue, System.out); 
    Consumer consumerB = new Consumer("ConsumerB", blockingQueue, System.out); 
    Consumer consumerC = new Consumer("ConsumerC", blockingQueue, System.out); 
    Consumer consumerD = new Consumer("ConsumerD", blockingQueue, System.out); 
    producer.start(); 
    consumerA.start(); 
    consumerB.start(); 
    consumerC.start(); 
    consumerD.start(); 



Figure 1. Consumer Threads: These threads dequeue messages from the BlockingQueue in the order that you spawned them. 
下面一行创建BlockingQueue: 


BlockingQueue<String> blockingQueue                     = new LinkedBlockingQueue<String>(); 

注意,它使用BlockingQueue的LinkedBlockingQueue实现。这是因为BlockingQueue是一个抽象类,你不 能直接实例化它。你也可以使用ArrayBlockingQueueQueue类型。ArrayBlockingQueue使用一个数组作为它的存储设 备,而LinkedBlockingQueue使用一个LinkedList。ArrayBlockingQueue的容量是固定的。对于 LinkedBlockingQueue,最大值可以指定;默认是无边界的。本示例代码采用无边界方式。 

在类的执行期间,从Queue中读取对象以顺序方式执行(见下面例子的执行)。实际上,一个consumer线程阻塞其他访问BlockingQueue的线程直到它可以从Queue中取出一个对象。 


DelayQueue-我是/不是不完整的 

在某些情况下,存放在Queue中的对象,在它们准备被取出之前,会需要被放在另一Queue中一段时间。这时你可使用 java.util.concurrent.DelayQueue类,他实现类BlockingQueue接口。DelayQueue需要Queue对象 被驻留在Queue上一段指定时间。 

我想用来证实它的现实例子(这可能是你非常渴望的)是关于松饼(muffins)。噢,Muffin对象(象我们正在谈论的Java-没有 coffee双关意图)。假定你有一个DelayQueue并在其中放了一些Muffin对象。Muffin对象(如下所示)必须实现 java.util.concurrent.Delayed接口,以便可被放在DelayQueue中。这个接口需要Muffin对象实现 getDelay方法(如下所示)。getDelay方法,实际上声明给多长时间让对象保存在DelayQueue中。当该方法返回的值变为0或小于0 时,对象就准备完毕(或在本例子中,是烤制完毕)并允许被取出(见Listing 3)。 

Muffin类也实现compareTo(java.util.concurrent.Delayed)方法。由于Delayed接口继承自java.lang.Comparable类,这通过约定限制你要实现Muffin对象的bakeCompletion时间。 

由于你不是真想去吃没有完全烤熟的Muffin,因此,需要将Muffin放在DelayQueue中存放推荐的烤制时间。Listing 4,取自DelayQueueUsageExample类,展示了从DelayQueue中enqueue和dequeue Muffin对象。 

如你所见,对Muffin对象的烤制时间是使用它的构造器设置的(构造器期望烤制时间是以秒计)。 

如前所讲,Muffin对象放到DelayQueue中是不允许被取出的,直到他的延时时间(又叫烤制时间)超期。元素被从Queue中取出基于 最早的延时时间。在本例中,如果你有一些已经烤过的Muffin对象,他们将按他们已经等待多久而被取出(换句话说,最早被烤制的Muffin会在新烤制 的Muffin之前被取出)。 

SynchronousQueue 

在Java 1.5中,另外一种阻塞Queue实现是SynchronousQueue。相当有趣的是,该Queue没有内在容量。这是故意的,因为Queue意在用于传递目的。这意味着,在一个同步Queue结构中,put请求必须等待来自另一线程的给SynchronousQueue的take请求。同时,一个take请求必须等待一个来自另一线程的给SynchronousQueue的put请求。用程序来示例此概念,可参见示例代码。类似于前边的LinkedBlockingQueue例子,它包含一个consumer(SynchConsumer),见Listing 5。 

Listing 5中的代码使用SynchronousQueue类的poll(long timeout,TimeUnit unit)方法。此方法允许poll过程在厌倦等待另一消费线程写SynchronousQueue之前等待一个指定时间(本例中是20秒)。 

在Listing 6中的producer(SynchProducer)使用相似的offer(E o,long timeout, TimeUnit unit)方法去放置对象到SynchronousQueue中。使用此方法允许在厌倦等待另一线程去读取SynchronousQueue之前等待一段时间(本例中为10秒)。 

TestSynchQueue 展示了producer和consumer的动作: 

import java.util.concurrent.SynchronousQueue; 
import java.util.concurrent.LinkedBlockingQueue; 

public class TestSynchQueue 

public static void main(String args[]) 

    SynchronousQueue<String> synchQueue = new SynchronousQueue<String>(); 
    SynchProducer producer = new SynchProducer("ProducerA",synchQueue, System.out); 
    SynchConsumer consumerA = new SynchConsumer("ConsumerA", synchQueue, System.out); 
    consumerA.start(); 
    producer.start(); 



当试图明白隐藏在SynchronousQueue后面的概念时,要牢记这些Queue通常被使用在什么地方。JavaDoc中关于同步Queue指出: 

"它们[同步Queue]是适合于传递设计,在那里运行在一个线程中的对象必须与运行在另外一个线程中的对象同步以便于交给它一些信息,时间或任务。"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值