Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型若是是Queue时,就彻底只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可使用。BlockingQueue 继承了Queue接口。前端
队列是一种特殊的线性表,它只容许在表的前端(front)进行删除操做,而在表的后端(rear)进行插入操做。进行插入操做的端称为队尾,进行删除操做的端称为队头。队列中没有元素时,称为空队列。java
队列是一种数据结构.它有两个基本操做:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,若是你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将致使线程阻塞.在多线程进行合做时,阻塞队列是颇有用的工具。工做者线程可 以按期地把中间结果存到阻塞队列中而其余工做者线线程把中间结果取出并在未来修改它们。队列会自动平衡负载。若是第一个线程集运行得比第二个慢,则第二个 线程集在等待结果时就会阻塞。若是第一个线程集运行得快,那么它将等待第二个线程集遇上来。下表显示了jdk1.5中的阻塞队列的操做:后端
add 增长一个元索 若是队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 若是队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 若是队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 若是队列已满,则返回false
poll 移除并返问队列头部的元素 若是队列为空,则返回null
peek 返回队列头部的元素 若是队列为空,则返回null
put 添加一个元素 若是队列满,则阻塞
take 移除并返回队列头部的元素 若是队列为空,则阻塞数组
remove、element、offer 、poll、peek 实际上是属于Queue接口。 数据结构
Queue使用时要尽可能避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。它们的优势是经过返回值能够判断成功与否,add()和remove()方法在失败的时候会抛出异常。 若是要使用前端而不移出该元素,使用
element()或者peek()方法。多线程
值得注意的是LinkedList类实现了Queue接口,所以咱们能够把LinkedList当成Queue来用。代码以下:工具
import java.util.LinkedList; import java.util.Queue; public class QueueTest { public static void main(String[] args) { //add()和remove()方法在失败的时候会抛出异常(不推荐) Queue<String> queue = new LinkedList<String>(); //添加元素 queue.offer("a"); queue.offer("b"); queue.offer("c"); queue.offer("d"); queue.offer("e"); for(String q : queue){ System.out.println(q); } System.out.println("==="); System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除 for(String q : queue){ System.out.println(q); } System.out.println("==="); System.out.println("element="+queue.element()); //返回第一个元素 for(String q : queue){ System.out.println(q); } System.out.println("==="); System.out.println("peek="+queue.peek()); //返回第一个元素 for(String q : queue){ System.out.println(q); } } }
阻塞队列的操做能够根据它们的响应方式分为如下三类:aad、removee和element操做在你试图为一个已满的队列增长元素或从空队列取得元素时 抛出异常。固然,在多线程程序中,队列在任什么时候间均可能变成满的或空的,因此你可能想使用offer、poll、peek方法。这些方法在没法完成任务时 只是给出一个出错示而不会抛出异常。性能
注意:poll和peek方法出错进返回null。所以,向队列中插入null值是不合法的。this
还有带超时的offer和poll方法变种,例如,下面的调用:
boolean success = q.offer(x,100,TimeUnit.MILLISECONDS);
尝试在100毫秒内向队列尾部插入一个元素。若是成功,当即返回true;不然,当到达超时进,返回false。一样地,调用:
Object head = q.poll(100, TimeUnit.MILLISECONDS);
若是在100毫秒内成功地移除了队列头元素,则当即返回头元素;不然在到达超时时,返回null。spa
最后,咱们有阻塞操做put和take。put方法在队列满时阻塞,take方法在队列空时阻塞。
java.ulil.concurrent包提供了阻塞队列的4个变种。默认状况下,LinkedBlockingQueue的容量是没有上限的(说的不许确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),可是也能够选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
ArrayBlockingQueue在构造时须要指定容量, 并能够选择是否须要公平性,若是公平参数被设置true,等待时间最长的线程会优先获得处理(其实就是经过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操做)。一般,公平性会使你在性能上付出代价,只有在的确很是须要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限(看了一下源码,PriorityBlockingQueue是对 PriorityQueue的再次包装,是基于堆数据结构的,而PriorityQueue是没有容量限制的,与ArrayList同样,因此在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,可是因为资源被耗尽,因此试图执行添加操做可能会致使 OutOfMemoryError),可是若是队列为空,那么取元素的操做take就会阻塞,因此它的检索操做take是受阻的。另外,往入该队列中的元 素要具备比较能力。
最后,DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。若是延迟都尚未期满,则队列没有头部,而且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不容许使用 null 元素。 下面是延迟接口:
public interface Delayed extends Comparable<Delayed> { long getDelay(TimeUnit unit); }
放入DelayQueue的元素还将要实现compareTo方法,DelayQueue使用这个来为元素排序。
下面的实例展现了如何使用阻塞队列来控制线程集。程序在一个目录及它的全部子目录下搜索全部文件,打印出包含指定关键字的文件列表。从下面实例能够看出,使用阻塞队列两个显著的好处就是:多线程操做共同的队列时不须要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减小两边的处理速度差距。下面是具体实现:
public class BlockingQueueTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter base directory (e.g. /usr/local/jdk5.0/src): "); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile): "); String keyword = in.nextLine(); final int FILE_QUEUE_SIZE = 10;// 阻塞队列大小 final int SEARCH_THREADS = 100;// 关键字搜索线程个数 // 基于ArrayBlockingQueue的阻塞队列 BlockingQueue<File> queue = new ArrayBlockingQueue<File>( FILE_QUEUE_SIZE); //只启动一个线程来搜索目录 FileEnumerationTask enumerator = new FileEnumerationTask(queue, new File(directory)); new Thread(enumerator).start(); //启动100个线程用来在文件中搜索指定的关键字 for (int i = 1; i <= SEARCH_THREADS; i++) new Thread(new SearchTask(queue, keyword)).start(); } } class FileEnumerationTask implements Runnable { //哑元文件对象,放在阻塞队列最后,用来标示文件已被遍历完 public static File DUMMY = new File(""); private BlockingQueue<File> queue; private File startingDirectory; public FileEnumerationTask(BlockingQueue<File> queue, File startingDirectory) { this.queue = queue; this.startingDirectory = startingDirectory; } public void run() { try { enumerate(startingDirectory); queue.put(DUMMY);//执行到这里说明指定的目录下文件已被遍历完 } catch (InterruptedException e) { } } // 将指定目录下的全部文件以File对象的形式放入阻塞队列中 public void enumerate(File directory) throws InterruptedException { File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) enumerate(file); else //将元素放入队尾,若是队列满,则阻塞 queue.put(file); } } } class SearchTask implements Runnable { private BlockingQueue<File> queue; private String keyword; public SearchTask(BlockingQueue<File> queue, String keyword) { this.queue = queue; this.keyword = keyword; } public void run() { try { boolean done = false; while (!done) { //取出队首元素,若是队列为空,则阻塞 File file = queue.take(); if (file == FileEnumerationTask.DUMMY) { //取出来后从新放入,好让其余线程读到它时也很快的结束 queue.put(file); done = true; } else search(file); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { } } public void search(File file) throws IOException { Scanner in = new Scanner(new FileInputStream(file)); int lineNumber = 0; while (in.hasNextLine()) { lineNumber++; String line = in.nextLine(); if (line.contains(keyword)) System.out.printf("%s:%d:%s%n", file.getPath(), lineNumber, line); } in.close(); } }