并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法

https://blog.csdn.net/heqifan19891115/article/details/72302990

https://blog.csdn.net/jameshadoop/article/details/52729796

本文转自:并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法

 

 

 

在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列(先进先出)。Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。

注:什么叫线程安全?这个首先要明确。线程安全就是说多线程访问同一代码,不会产生不确定的结果。

并行和并发区别

1、并行是指两者同时执行一件事,比如赛跑,两个人都在不停的往前跑;
2、并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率

LinkedBlockingQueue
由于LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。

 
  1. package cn.thread;

  2.  
  3. import java.util.concurrent.BlockingQueue;

  4. import java.util.concurrent.ExecutorService;

  5. import java.util.concurrent.Executors;

  6. import java.util.concurrent.LinkedBlockingQueue;

  7.  
  8. /**

  9. * 多线程模拟实现生产者/消费者模型

  10. *

  11. * @author 林计钦

  12. * @version 1.0 2013-7-25 下午05:23:11

  13. */

  14. public class BlockingQueueTest2 {

  15. /**

  16. *

  17. * 定义装苹果的篮子

  18. *

  19. */

  20. public class Basket {

  21. // 篮子,能够容纳3个苹果

  22. BlockingQueue<String> basket = new LinkedBlockingQueue<String>(3);

  23.  
  24. // 生产苹果,放入篮子

  25. public void produce() throws InterruptedException {

  26. // put方法放入一个苹果,若basket满了,等到basket有位置

  27. basket.put("An apple");

  28. }

  29.  
  30. // 消费苹果,从篮子中取走

  31. public String consume() throws InterruptedException {

  32. // take方法取出一个苹果,若basket为空,等到basket有苹果为止(获取并移除此队列的头部)

  33. return basket.take();

  34. }

  35. }

  36.  
  37. // 定义苹果生产者

  38. class Producer implements Runnable {

  39. private String instance;

  40. private Basket basket;

  41.  
  42. public Producer(String instance, Basket basket) {

  43. this.instance = instance;

  44. this.basket = basket;

  45. }

  46.  
  47. public void run() {

  48. try {

  49. while (true) {

  50. // 生产苹果

  51. System.out.println("生产者准备生产苹果:" + instance);

  52. basket.produce();

  53. System.out.println("!生产者生产苹果完毕:" + instance);

  54. // 休眠300ms

  55. Thread.sleep(300);

  56. }

  57. } catch (InterruptedException ex) {

  58. System.out.println("Producer Interrupted");

  59. }

  60. }

  61. }

  62.  
  63. // 定义苹果消费者

  64. class Consumer implements Runnable {

  65. private String instance;

  66. private Basket basket;

  67.  
  68. public Consumer(String instance, Basket basket) {

  69. this.instance = instance;

  70. this.basket = basket;

  71. }

  72.  
  73. public void run() {

  74. try {

  75. while (true) {

  76. // 消费苹果

  77. System.out.println("消费者准备消费苹果:" + instance);

  78. System.out.println(basket.consume());

  79. System.out.println("!消费者消费苹果完毕:" + instance);

  80. // 休眠1000ms

  81. Thread.sleep(1000);

  82. }

  83. } catch (InterruptedException ex) {

  84. System.out.println("Consumer Interrupted");

  85. }

  86. }

  87. }

  88.  
  89. public static void main(String[] args) {

  90. BlockingQueueTest2 test = new BlockingQueueTest2();

  91.  
  92. // 建立一个装苹果的篮子

  93. Basket basket = test.new Basket();

  94.  
  95. ExecutorService service = Executors.newCachedThreadPool();

  96. Producer producer = test.new Producer("生产者001", basket);

  97. Producer producer2 = test.new Producer("生产者002", basket);

  98. Consumer consumer = test.new Consumer("消费者001", basket);

  99. service.submit(producer);

  100. service.submit(producer2);

  101. service.submit(consumer);

  102. // 程序运行5s后,所有任务停止

  103. // try {

  104. // Thread.sleep(1000 * 5);

  105. // } catch (InterruptedException e) {

  106. // e.printStackTrace();

  107. // }

  108. // service.shutdownNow();

  109. }

  110.  
  111. }


 

ConcurrentLinkedQueue
ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。
LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。

 

 

 
  1. package cn.thread;

  2.  
  3. import java.util.concurrent.ConcurrentLinkedQueue;

  4. import java.util.concurrent.CountDownLatch;

  5. import java.util.concurrent.ExecutorService;

  6. import java.util.concurrent.Executors;

  7.  
  8. public class ConcurrentLinkedQueueTest {

  9. private static ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<Integer>();

  10. private static int count = 2; // 线程个数

  11. //CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

  12. private static CountDownLatch latch = new CountDownLatch(count);

  13.  
  14. public static void main(String[] args) throws InterruptedException {

  15. long timeStart = System.currentTimeMillis();

  16. ExecutorService es = Executors.newFixedThreadPool(4);

  17. ConcurrentLinkedQueueTest.offer();

  18. for (int i = 0; i < count; i++) {

  19. es.submit(new Poll());

  20. }

  21. latch.await(); //使得主线程(main)阻塞直到latch.countDown()为零才继续执行

  22. System.out.println("cost time " + (System.currentTimeMillis() - timeStart) + "ms");

  23. es.shutdown();

  24. }

  25.  
  26. /**

  27. * 生产

  28. */

  29. public static void offer() {

  30. for (int i = 0; i < 100000; i++) {

  31. queue.offer(i);

  32. }

  33. }

  34.  
  35.  
  36. /**

  37. * 消费

  38. *

  39. * @author 林计钦

  40. * @version 1.0 2013-7-25 下午05:32:56

  41. */

  42. static class Poll implements Runnable {

  43. public void run() {

  44. // while (queue.size()>0) {

  45. while (!queue.isEmpty()) {

  46. System.out.println(queue.poll());

  47. }

  48. latch.countDown();

  49. }

  50. }

  51. }

 

 

运行结果:
costtime 2360ms

改用while (queue.size()>0)后
运行结果:
cost time 46422ms

结果居然相差那么大,看了下ConcurrentLinkedQueue的API原来.size()是要遍历一遍集合的,难怪那么慢,所以尽量要避免用size而改用isEmpty().

 

关于ConcurrentLinkedQueue一个不错的例子:

http://blog.csdn.net/rsun1/article/details/8186694

 

ConcurrentLinkedQueue、AraayBlockingQueue、LinkedBlockingQueue 区别及使用场景

三者区别与联系:

联系,三者 都是线程安全的。区别,就是 并发 和 阻塞,前者为并发队列,因为采用cas算法,所以能够高并发的处理;后2者采用锁机制,所以是阻塞的。注意点就是前者由于采用cas算法,虽然能高并发,但cas的特点造成操作的危险性,怎么危险性可以去查一下cas算法(但一些多消费性的队列还是用的它,原因看下边使用场景中的说明)

后2者区别:

联系,第2和第3都是阻塞队列,都是采用锁,都有阻塞容器Condition,通过Condition阻塞容量为空时的取操作和容量满时的写操作第。区别,第2就一个整锁,第3是2个锁,所以第2第3的锁机制不一样,第3比第2吞吐量 大,并发性能也比第2高。 
后2者的具体信息: LinkedBlockingQueue是BlockingQueue的一种使用Link List的实现,它对头和尾(取和添加操作)采用两把不同的锁,相对于ArrayBlockingQueue提高了吞吐量。它也是一种阻塞型的容器,适合于实现“消费者生产者”模式。 
ArrayBlockingQueue是对BlockingQueue的一个数组实现,它使用一把全局的锁并行对queue的读写操作,同时使用两个Condition阻塞容量为空时的取操作和容量满时的写操作。

正因为LinkedBlockingQueue使用两个独立的锁控制数据同步,所以可以使存取两种操作并行执行,从而提高并发效率。而ArrayBlockingQueue使用一把锁,造成在存取两种操作争抢一把锁,而使得性能相对低下。LinkedBlockingQueue可以不设置队列容量,默认为Integer.MAX_VALUE.其容易造成内存溢出,一般要设置其值。

使用场景:

适用阻塞队列的好处:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距,自动平衡负载这个特性就造成它能被用于多生产者队列,因为你生成多了(队列满了)你就要阻塞等着,直到消费者消费使队列不满你才可以继续生产。 当许多线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。 
LinkedBlockingQueue 多用于任务队列(单线程发布任务,任务满了就停止等待阻塞,当任务被完成消费少了又开始负载 发布任务) 
ConcurrentLinkedQueue 多用于消息队列(多个线程发送消息,先随便发来,不计并发的-cas特点)

多个生产者,对于LBQ性能还算可以接受;但是多个消费者就不行了mainLoop需要一个timeout的机制,否则空转,cpu会飙升的。LBQ正好提供了timeout的接口,更方便使用 如果CLQ,那么我需要收到处理sleep

总结

单生产者,单消费者 用 LinkedBlockingqueue 
多生产者,单消费者 用 LinkedBlockingqueue 
单生产者 ,多消费者 用 ConcurrentLinkedQueue 
多生产者 ,多消费者 用 ConcurrentLinkedQueue 
对上边总结: 
如消息队列,好多client发来消息,根据client发送先后放入队列中,先发送的就先放进来,然后由于队列是先进先出,是一个一个出来的,所以不涉及到线程安全问题,所以用LinkedBlockingqueue 队列。比如还拿上边消息队列那个例子,由于队列是一个一个出来的,出来一个消息协议体就由线程池分配一个线程去处理这个消息体,这个消息体对于线程池来说谈不上共享不共享的问题,即不会多个线程去抢同一个消息体去执行,所以就不需要用线程安全的队列结构了;那假如一种情况,队列里仍然是一个一个的出来,但是出来的这个元素是 线程池共享的,即大家线程都需要用到这个从队列里出来的这个元素,也就是多消费者消费同一个东西这种情况,所以就要用线程安全的队列了,即ConcurrentLinkedQueue。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值