Java线程外篇:阻塞队列BlockingQueue

 好久没有写文章了,这段时间事情比较杂,工作也比较杂乱,上周日刚搬完家,从自建房搬到了楼房,提升了一层生活品质,哈哈!不过昨天晚上在公交车上钱包被偷了,前段时间还丢个自行车,不得不感叹,京城扒手真多,还无人处理。言归正传,这一段时间我的工作主要是改进公司的调度器,调度器调度线程池执行任务,生产者生产任务,消费者消费任务,那么这时就需要一个任务队列,生产者向队列里插入任务,消费者从队列里提取任务执行,调度器里是通过BlockingQueue实现的队列,随后小查一下,下面看看BlockingQueue的原理及其方法。

       BlockingQueue最终会有四种状况,抛出异常、返回特殊值、阻塞、超时,下表总结了这些方法:

  抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用

       BlockingQueue是个接口,有如下实现类:

       1. ArrayBlockQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象必须明确大小,像数组一样。

       2. LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象如果没有明确大小,默认值是Integer.MAX_VALUE。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。 

       3. PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

       4. SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必须等待另一个线程移除,反之亦然。

       下面使用ArrayBlockQueue来实现之前实现过的生产者消/费者模式,代码如下:

[java]  view plain copy print ?
  1. /** 定义一个盘子类,可以放鸡蛋和取鸡蛋 */  
  2. public class BigPlate {  
  3.   
  4.     /** 装鸡蛋的盘子,大小为5 */  
  5.     private BlockingQueue<Object> eggs = new ArrayBlockingQueue<Object>(5);  
  6.       
  7.     /** 放鸡蛋 */  
  8.     public void putEgg(Object egg) {  
  9.         try {  
  10.             eggs.put(egg);// 向盘子末尾放一个鸡蛋,如果盘子满了,当前线程阻塞  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.   
  15.         // 下面输出有时不准确,因为与put操作不是一个原子操作  
  16.         System.out.println("放入鸡蛋");  
  17.     }  
  18.       
  19.     /** 取鸡蛋 */  
  20.     public Object getEgg() {  
  21.         Object egg = null;  
  22.         try {  
  23.             egg = eggs.take();// 从盘子开始取一个鸡蛋,如果盘子空了,当前线程阻塞  
  24.         } catch (InterruptedException e) {  
  25.             e.printStackTrace();  
  26.         }  
  27.   
  28.         // 下面输出有时不准确,因为与take操作不是一个原子操作  
  29.         System.out.println("拿到鸡蛋");  
  30.         return egg;  
  31.     }  
  32.       
  33.     /** 放鸡蛋线程 */  
  34.     static class AddThread extends Thread {  
  35.         private BigPlate plate;  
  36.         private Object egg = new Object();  
  37.   
  38.         public AddThread(BigPlate plate) {  
  39.             this.plate = plate;  
  40.         }  
  41.   
  42.         public void run() {  
  43.             plate.putEgg(egg);  
  44.         }  
  45.     }  
  46.   
  47.     /** 取鸡蛋线程 */  
  48.     static class GetThread extends Thread {  
  49.         private BigPlate plate;  
  50.   
  51.         public GetThread(BigPlate plate) {  
  52.             this.plate = plate;  
  53.         }  
  54.   
  55.         public void run() {  
  56.             plate.getEgg();  
  57.         }  
  58.     }  
  59.       
  60.     public static void main(String[] args) {  
  61.         BigPlate plate = new BigPlate();  
  62.         // 先启动10个放鸡蛋线程  
  63.         for(int i = 0; i < 10; i++) {  
  64.             new Thread(new AddThread(plate)).start();  
  65.         }  
  66.         // 再启动10个取鸡蛋线程  
  67.         for(int i = 0; i < 10; i++) {  
  68.             new Thread(new GetThread(plate)).start();  
  69.         }  
  70.     }  
  71. }  
       执行结果:

[plain]  view plain copy print ?
  1. 放入鸡蛋  
  2. 放入鸡蛋  
  3. 放入鸡蛋  
  4. 放入鸡蛋  
  5. 放入鸡蛋  
  6. 拿到鸡蛋  
  7. 放入鸡蛋  
  8. 拿到鸡蛋  
  9. 拿到鸡蛋  
  10. 拿到鸡蛋  
  11. 放入鸡蛋  
  12. 放入鸡蛋  
  13. 放入鸡蛋  
  14. 拿到鸡蛋  
  15. 放入鸡蛋  
  16. 拿到鸡蛋  
  17. 拿到鸡蛋  
  18. 拿到鸡蛋  
  19. 拿到鸡蛋  
  20. 拿到鸡蛋  
       从结果看,启动10个放鸡蛋线程和10个取鸡蛋线程,前5个放入鸡蛋的线程成功执行,到第6个,发现盘子满了,阻塞住,这时切换到取鸡蛋线程执行,成功实现了生产者/消费者模式。java.util.concurrent包是个强大的包!

你可能感兴趣的:(java,线程安全,阻塞队列)