java中queue的使用

转自;java中queue的使用

Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口。

 

队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具。工作者线程可 以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个 线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。下表显示了jdk1.5中的阻塞队列的操作:

 

add        增加一个元索                     如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove   移除并返回队列头部的元素    如果队列为空,则抛出一个NoSuchElementException异常
element  返回队列头部的元素             如果队列为空,则抛出一个NoSuchElementException异常
offer       添加一个元素并返回true       如果队列已满,则返回false
poll         移除并返问队列头部的元素    如果队列为空,则返回null
peek       返回队列头部的元素             如果队列为空,则返回null
put         添加一个元素                      如果队列满,则阻塞
take        移除并返回队列头部的元素     如果队列为空,则阻塞

 

remove、element、offer 、poll、peek 其实是属于Queue接口。 

 

阻塞队列的操作可以根据它们的响应方式分为以下三类:aad、removee和element操作在你试图为一个已满的队列增加元素或从空队列取得元素时 抛出异常。当然,在多线程程序中,队列在任何时间都可能变成满的或空的,所以你可能想使用offer、poll、peek方法。这些方法在无法完成任务时 只是给出一个出错示而不会抛出异常。

 

注意:poll和peek方法出错进返回null。因此,向队列中插入null值是不合法的。

 

还有带超时的offer和poll方法变种,例如,下面的调用:
boolean success = q.offer(x,100,TimeUnit.MILLISECONDS);
尝试在100毫秒内向队列尾部插入一个元素。如果成功,立即返回true;否则,当到达超时进,返回false。同样地,调用:
Object head = q.poll(100, TimeUnit.MILLISECONDS);
如果在100毫秒内成功地移除了队列头元素,则立即返回头元素;否则在到达超时时,返回null。

 

最后,我们有阻塞操作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 元素。 下面是延迟接口:

Java代码
  1. public interface Delayed extends Comparable<Delayed>  
  2.      long getDelay(TimeUnit unit);  
  3.  

放入DelayQueue的元素还将要实现compareTo方法,DelayQueue使用这个来为元素排序。

 

下面的实例展示了如何使用阻塞队列来控制线程集。程序在一个目录及它的所有子目录下搜索所有文件,打印出包含指定关键字的文件列表。从下面实例可以看出,使用阻塞队列两个显著的好处就是:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。下面是具体实现:

Java代码
  1. public class BlockingQueueTest  
  2.     public static void main(String[] args)  
  3.         Scanner in new Scanner(System.in);  
  4.         System.out.print("Enter base directory (e.g. /usr/local/jdk5.0/src): ");  
  5.         String directory in.nextLine();  
  6.         System.out.print("Enter keyword (e.g. volatile): ");  
  7.         String keyword in.nextLine();  
  8.   
  9.         final int FILE_QUEUE_SIZE 10;// 阻塞队列大小  
  10.         final int SEARCH_THREADS 100;// 关键字搜索线程个数  
  11.   
  12.         // 基于ArrayBlockingQueue的阻塞队列  
  13.         BlockingQueue<File> queue new ArrayBlockingQueue<File>(  
  14.                 FILE_QUEUE_SIZE);  
  15.   
  16.         //只启动一个线程来搜索目录  
  17.         FileEnumerationTask enumerator new FileEnumerationTask(queue,  
  18.                 new File(directory));  
  19.         new Thread(enumerator).start();  
  20.           
  21.         //启动100个线程用来在文件中搜索指定的关键字  
  22.         for (int 1<= SEARCH_THREADS; i++)  
  23.             new Thread(new SearchTask(queue, keyword)).start();  
  24.      
  25.  
  26. class FileEnumerationTask implements Runnable  
  27.     //哑元文件对象,放在阻塞队列最后,用来标示文件已被遍历完  
  28.     public static File DUMMY new File("");  
  29.   
  30.     private BlockingQueue<File> queue;  
  31.     private File startingDirectory;  
  32.   
  33.     public FileEnumerationTask(BlockingQueue<File> queue, File startingDirectory)  
  34.         this.queue queue;  
  35.         this.startingDirectory startingDirectory;  
  36.      
  37.   
  38.     public void run()  
  39.         try  
  40.             enumerate(startingDirectory);  
  41.             queue.put(DUMMY);//执行到这里说明指定的目录下文件已被遍历完  
  42.         catch (InterruptedException e)  
  43.          
  44.      
  45.   
  46.     // 将指定目录下的所有文件以File对象的形式放入阻塞队列中  
  47.     public void enumerate(File directory) throws InterruptedException  
  48.         File[] files directory.listFiles();  
  49.         for (File file files)  
  50.             if (file.isDirectory())  
  51.                 enumerate(file);  
  52.             else  
  53.                 //将元素放入队尾,如果队列满,则阻塞  
  54.                 queue.put(file);  
  55.          
  56.      
  57.  
  58. class SearchTask implements Runnable  
  59.     private BlockingQueue<File> queue;  
  60.     private String keyword;  
  61.   
  62.     public SearchTask(BlockingQueue<File> queue, String keyword)  
  63.         this.queue queue;  
  64.         this.keyword keyword;  
  65.      
  66.   
  67.     public void run()  
  68.         try  
  69.             boolean done false 
  70.             while (!done)  
  71.                 //取出队首元素,如果队列为空,则阻塞  
  72.                 File file queue.take();  
  73.                 if (file == FileEnumerationTask.DUMMY)  
  74.                     //取出来后重新放入,好让其他线程读到它时也很快的结束  
  75.                     queue.put(file);  
  76.                     done true 
  77.                 else  
  78.                     search(file);  
  79.              
  80.         catch (IOException e)  
  81.             e.printStackTrace();  
  82.         catch (InterruptedException e)  
  83.          
  84.      
  85.     public void search(File file) throws IOException  
  86.         Scanner in new Scanner(new FileInputStream(file));  
  87.         int lineNumber 0 
  88.         while (in.hasNextLine())  
  89.             lineNumber++;  
  90.             String line in.nextLine();  
  91.             if (line.contains(keyword))  
  92.                 System.out.printf("%s:%d:%s%n"file.getPath(), lineNumber,  
  93.                         line);  
  94.          
  95.         in.close();  
  96.      
  97.  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

自:http://janeky.iteye.com/blog/770671
7.BlockingQueue 
    “支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。“ 

    这里我们主要讨论BlockingQueue的最典型实现:LinkedBlockingQueue 和ArrayBlockingQueue。两者的不同是底层的数据结构不够,一个是链表,另外一个是数组。 
    
    后面将要单独解释其他类型的BlockingQueue和SynchronousQueue 

     BlockingQueue的经典用途是 生产者-消费者模式 

    代码如下: 
Java代码  
  1. import java.util.Random;  
  2. import java.util.concurrent.BlockingQueue;  
  3. import java.util.concurrent.LinkedBlockingQueue;  
  4.   
  5. public class TestBlockingQueue {  
  6.   
  7.     public static void main(String[] args) {  
  8.         final BlockingQueue<Integer> queue=new LinkedBlockingQueue<Integer>(3);  
  9.         final Random random=new Random();  
  10.           
  11.         class Producer implements Runnable{  
  12.             @Override  
  13.             public void run() {  
  14.                 while(true){  
  15.                     try {  
  16.                         int i=random.nextInt(100);  
  17.                         queue.put(i);//当队列达到容量时候,会自动阻塞的  
  18.                         if(queue.size()==3)  
  19.                         {  
  20.                             System.out.println("full");  
  21.                         }  
  22.                     } catch (InterruptedException e) {  
  23.                         e.printStackTrace();  
  24.                     }  
  25.                 }  
  26.             }  
  27.         }  
  28.           
  29.         class Consumer implements Runnable{  
  30.             @Override  
  31.             public void run() {  
  32.                 while(true){  
  33.                     try {  
  34.                         queue.take();//当队列为空时,也会自动阻塞  
  35.                         Thread.sleep(1000);  
  36.                     } catch (InterruptedException e) {  
  37.                         e.printStackTrace();  
  38.                     }  
  39.                 }  
  40.             }  
  41.         }  
  42.           
  43.         new Thread(new Producer()).start();  
  44.         new Thread(new Consumer()).start();  
  45.     }  
  46.   
  47. }  

    总结:BlockingQueue使用时候特别注意take 和 put 

8. DelayQueue 

我们先来学习一下JDK1.5 API中关于这个类的详细介绍: 
    “它是包含Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即使无法使用 take 或 poll 移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size 方法同时返回到期和未到期元素的计数。此队列不允许使用 null 元素。” 

    在现实生活中,很多DelayQueue的例子。就拿上海的SB会来说明,很多国家地区的开馆时间不同。你很早就来到园区,然后急急忙忙地跑到一些心仪的馆区,发现有些还没开,你吃了闭门羹。 

    仔细研究DelayQueue,你会发现它其实就是一个PriorityQueue的封装(按照delay时间排序),里面的元素都实现了Delayed接口,相关操作需要判断延时时间是否到了。 

    在实际应用中,有人拿它来管理跟实际相关的缓存、session等 

    下面我就通过 “上海SB会的例子来阐述DelayQueue的用法” 

代码如下: 
Java代码  
  1. import java.util.Random;  
  2. import java.util.concurrent.DelayQueue;  
  3. import java.util.concurrent.Delayed;  
  4. import java.util.concurrent.TimeUnit;  
  5.   
  6. public class TestDelayQueue {  
  7.   
  8.     private class Stadium implements Delayed  
  9.     {  
  10.         long trigger;  
  11.           
  12.         public Stadium(long i){  
  13.             trigger=System.currentTimeMillis()+i;  
  14.         }  
  15.           
  16.         @Override  
  17.         public long getDelay(TimeUnit arg0) {  
  18.             long n=trigger-System.currentTimeMillis();  
  19.             return n;  
  20.         }  
  21.   
  22.         @Override  
  23.         public int compareTo(Delayed arg0) {  
  24.             return (int)(this.getDelay(TimeUnit.MILLISECONDS)-arg0.getDelay(TimeUnit.MILLISECONDS));  
  25.         }  
  26.           
  27.         public long getTriggerTime(){  
  28.             return trigger;  
  29.         }  
  30.           
  31.     }  
  32.     public static void main(String[] args)throws Exception {  
  33.         Random random=new Random();  
  34.         DelayQueue<Stadium> queue=new DelayQueue<Stadium>();  
  35.         TestDelayQueue t=new TestDelayQueue();  
  36.           
  37.         for(int i=0;i<5;i++){  
  38.             queue.add(t.new Stadium(random.nextInt(30000)));  
  39.         }  
  40.         Thread.sleep(2000);  
  41.           
  42.         while(true){  
  43.             Stadium s=queue.take();//延时时间未到就一直等待  
  44.             if(s!=null){  
  45.                 System.out.println(System.currentTimeMillis()-s.getTriggerTime());//基本上是等于0  
  46.             }  
  47.             if(queue.size()==0)  
  48.                 break;  
  49.         }  
  50.     }  
  51. }  


    总结:适用于需要延时操作的队列管理 


9. SynchronousQueue 
    我们先来学习一下JDK1.5 API中关于这个类的详细介绍: 

    “一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没 有元素可用于迭代。队列的头 是尝试添加到队列中的首个已排队插入线程的元素;如果没有这样的已排队线程,则没有可用于移除的元素并且 poll() 将会返回 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空 collection。此队列不允许 null 元素。 
    同步队列类似于 CSP 和 Ada 中使用的 rendezvous 信道。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。 “ 

    看起来很有意思吧。队列竟然是没有内部容量的。这个队列其实是BlockingQueue的一种实现。每个插入操作必须等待另一个线程的对应移除操作,反之亦然。它给我们提供了在线程之间交换单一元素的极轻量级方法 

    应用举例:我们要在多个线程中传递一个变量。 

   代码如下(其实就是生产者消费者模式) 
Java代码  
  1. import java.util.Arrays;  
  2. import java.util.List;  
  3. import java.util.concurrent.BlockingQueue;  
  4. import java.util.concurrent.SynchronousQueue;  
  5.   
  6. public class TestSynchronousQueue {  
  7.   
  8.     class Producer implements Runnable {  
  9.         private BlockingQueue<String> queue;  
  10.         List<String> objects = Arrays.asList("one", "two", "three");  
  11.   
  12.         public Producer(BlockingQueue<String> q) {  
  13.             this.queue = q;  
  14.         }  
  15.   
  16.         @Override  
  17.         public void run() {  
  18.             try {  
  19.                 for (String s : objects) {  
  20.                     queue.put(s);// 产生数据放入队列中  
  21.                     System.out.printf("put:%s%n",s);  
  22.                 }  
  23.                 queue.put("Done");// 已完成的标志  
  24.             } catch (InterruptedException e) {  
  25.                 e.printStackTrace();  
  26.             }  
  27.         }  
  28.     }  
  29.   
  30.     class Consumer implements Runnable {  
  31.         private BlockingQueue<String> queue;  
  32.   
  33.         public Consumer(BlockingQueue<String> q) {  
  34.             this.queue = q;  
  35.         }  
  36.   
  37.         @Override  
  38.         public void run() {  
  39.             String obj = null;  
  40.             try {  
  41.                 while (!((obj = queue.take()).equals("Done"))) {  
  42.                     System.out.println(obj);//从队列中读取对象  
  43.                     Thread.sleep(3000);     //故意sleep,证明Producer是put不进去的  
  44.                 }  
  45.             } catch (InterruptedException e) {  
  46.                 e.printStackTrace();  
  47.             }  
  48.         }  
  49.     }  
  50.   
  51.     public static void main(String[] args) {  
  52.         BlockingQueue<String> q=new SynchronousQueue<String>();  
  53.         TestSynchronousQueue t=new TestSynchronousQueue();  
  54.         new Thread(t.new Producer(q)).start();  
  55.         new Thread(t.new Consumer(q)).start();  
  56.     }  
  57.   
  58. }  


以下引用自:http://www.cnblogs.com/jackyuj/archive/2010/11/24/1886553.html
其中有很形象的解释和示例
  • 前言:

     在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列 类,为我们快速搭建高质量的多线程程序带来极大的便利。本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场 景。

  • 认识BlockingQueue
    阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示:

    从上图我们可以很清楚看到,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
    常用的队列主要有以下两种:(当然通过不同的实现方式,还可以延伸出很多不同类型的队列,DelayQueue就是其中的一种)
      先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性。
      后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。

          多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若 干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的 数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度, 并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而, 在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小 的复杂度。好在此时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。(在多线程领域:所谓阻塞,在某些 情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒)
    下面两幅图演示了BlockingQueue的两个常见阻塞场景:
           如上图所示:当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。


       如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
         这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞 线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。既然BlockingQueue如此神通广大,让我们一起来见识下 它的常用方法:
    BlockingQueue的核心方法
    放入数据:
      offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,
        则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
      offer(E o, long timeout, TimeUnit unit),可以设定等待的时间,如果在指定的时间内,还不能往队列中
        加入BlockingQueue,则返回失败。
      put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断
        直到BlockingQueue里面有空间再继续.
    获取数据:
      poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,
        取不到时返回null;
      poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,
        队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
      take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到
        BlockingQueue有新的数据被加入; 
      drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数), 
        通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
  • 常见BlockingQueue
    在了解了BlockingQueue的基本功能后,让我们来看看BlockingQueue家庭大致有哪些成员? 
     
  • BlockingQueue成员详细介绍
    1. ArrayBlockingQueue

          基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了 一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
       ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的 完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额 外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任 何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而 在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

    2. LinkedBlockingQueue
          基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列 中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时 (LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反 之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别 采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
    作为开 发 者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认 一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统 内存就有可能已被消耗殆尽了。

    ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。

    下面的代码演示了如何使用BlockingQueue:
    01 import java.util.concurrent.BlockingQueue;
    02 import java.util.concurrent.ExecutorService;
    03 import java.util.concurrent.Executors;
    04 import java.util.concurrent.LinkedBlockingQueue;
    05  
    06 /**
    07  * @author jackyuj
    08  */
    09 public class BlockingQueueTest {
    10  
    11     public static void main(String[] args) throws InterruptedException {
    12         // 声明一个容量为10的缓存队列
    13         BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
    14  
    15         Producer producer1 = new Producer(queue);
    16         Producer producer2 = new Producer(queue);
    17         Producer producer3 = new Producer(queue);
    18         Consumer consumer = new Consumer(queue);
    19  
    20         // 借助Executors
    21         ExecutorService service = Executors.newCachedThreadPool();
    22         // 启动线程
    23         service.execute(producer1);
    24         service.execute(producer2);
    25         service.execute(producer3);
    26         service.execute(consumer);
    27  
    28         // 执行10s
    29         Thread.sleep(10 1000);
    30         producer1.stop();
    31         producer2.stop();
    32         producer3.stop();
    33  
    34         Thread.sleep(2000);
    35         // 退出Executor
    36         service.shutdown();
    37     }
    38 }
    01 import java.util.Random;
    02 import java.util.concurrent.BlockingQueue;
    03 import java.util.concurrent.TimeUnit;
    04  
    05 /**
    06  * 消费者线程
    07  *
    08  * @author jackyuj
    09  */
    10 public class Consumer implements Runnable {
    11  
    12     public Consumer(BlockingQueue<String> queue) {
    13         this.queue = queue;
    14     }
    15  
    16     public void run() {
    17         System.out.println("启动消费者线程!");
    18         Random r = new Random();
    19         boolean isRunning = true;
    20         try {
    21             while (isRunning) {
    22                 System.out.println("正从队列获取数据...");
    23                 String data = queue.poll(2, TimeUnit.SECONDS);
    24                 if (null != data) {
    25                     System.out.println("拿到数据:" + data);
    26                     System.out.println("正在消费数据:" + data);
    27                     Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
    28                 else {
    29                     // 超过2s还没数据,认为所有生产线程都已经退出,自动退出消费线程。
    30                     isRunning = false;
    31                 }
    32             }
    33         catch (InterruptedException e) {
    34             e.printStackTrace();
    35             Thread.currentThread().interrupt();
    36         finally {
    37             System.out.println("退出消费者线程!");
    38         }
    39     }
    40  
    41     private BlockingQueue<String> queue;
    42     private static final int      DEFAULT_RANGE_FOR_SLEEP = 1000;
    43 }
    44  
    45 import java.util.Random;
    46 import java.util.concurrent.BlockingQueue;
    47 import java.util.concurrent.TimeUnit;
    48 import java.util.concurrent.atomic.AtomicInteger;
    49  
    50 /**
    51  * 生产者线程
    52  *
    53  * @author jackyuj
    54  */
    55 public class Producer implements Runnable {
    56  
    57     public Producer(BlockingQueue queue) {
    58         this.queue = queue;
    59     }
    60  
    61     public void run() {
    62         String data = null;
    63         Random r = new Random();
    64  
    65         System.out.println("启动生产者线程!");
    66         try {
    67             while (isRunning) {
    68                 System.out.println("正在生产数据...");
    69                 Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
    70  
    71                 data = "data:" + count.incrementAndGet();
    72                 System.out.println("将数据:" + data + "放入队列...");
    73                 if (!queue.offer(data, 2, TimeUnit.SECONDS)) {
    74                     System.out.println("放入数据失败:" + data);
    75                 }
    76             }
    77         catch (InterruptedException e) {
    78             e.printStackTrace();
    79             Thread.currentThread().interrupt();
    80         finally {
    81             System.out.println("退出生产者线程!");
    82         }
    83     }
    84  
    85     public void stop() {
    86         isRunning = false;
    87     }
    88  
    89     private volatile boolean      isRunning               = true;
    90     private BlockingQueue queue;
    91     private static AtomicInteger  count                   = new AtomicInteger();
    92     private static final int      DEFAULT_RANGE_FOR_SLEEP = 1000;
    93  
    94 }
  • 3. DelayQueue
          DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
    使用场景:
      DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。

    4. PriorityBlockingQueue
          基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不 会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度, 否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

    5. SynchronousQueue
          一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去 集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中 间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商 品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者 中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。
      声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:
      如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;
       但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的 生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
  • 小结
      BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待于唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值