为了账号安全,请及时绑定邮箱和手机立即绑定

阻塞队列之LinkedBlockingQueue

标签:
Java

概述

LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。LinkedBlockingQueue采用可重入锁(ReentrantLock)来保证在并发情况下的线程安全。

构造器

LinkedBlockingQueue一共有三个构造器,分别是无参构造器、可以指定容量的构造器、可以穿入一个容器的构造器。如果在创建实例的时候调用的是无参构造器,LinkedBlockingQueue的默认容量是Integer.MAX_VALUE,这样做很可能会导致队列还没有满,但是内存却已经满了的情况(内存溢出)。

1 public LinkedBlockingQueue();   //设置容量为Integer.MAX2 3 public LinkedBlockingQueue(int capacity);  //设置指定容量4 5 public LinkedBlockingQueue(Collection<? extends E> c);  //穿入一个容器,如果调用该构造器,容量默认也是Integer.MAX_VALUE

LinkedBlockingQueue常用操作

取数据

take():首选。当队列为空时阻塞

poll():弹出队顶元素,队列为空时,返回空

peek():和poll烈性,返回队队顶元素,但顶元素不弹出。队列为空时返回null

remove(Object o):移除某个元素,队列为空时抛出异常。成功移除返回true

 

添加数据

put():首选。队满是阻塞

offer():队满时返回false

 

判断队列是否为空

size()方法会遍历整个队列,时间复杂度为O(n),所以最好选用isEmtpy

 

put元素原理

基本过程:

1.判断元素是否为null,为null抛出异常

2.加锁(可中断锁)

3.判断队列长度是否到达容量,如果到达一直等待

4.如果没有队满,enqueue()在队尾加入元素

5.队列长度加1,此时如果队列还没有满,调用signal唤醒其他堵塞队列

复制代码

 1  if (e == null) throw new NullPointerException(); 2         3         int c = -1; 4         Node<E> node = new Node<E>(e); 5         final ReentrantLock putLock = this.putLock; 6         final AtomicInteger count = this.count; 7         putLock.lockInterruptibly(); 8         try { 9             while (count.get() == capacity) {10                 notFull.await();11             }12             enqueue(node);13             c = count.getAndIncrement();14             if (c + 1 < capacity)15                 notFull.signal();16         } finally {17             putLock.unlock();18         }

复制代码

 

take元素原理

 基本过程:

1.加锁(依旧是ReentrantLock),注意这里的锁和写入是不同的两把锁

2.判断队列是否为空,如果为空就一直等待

3.通过dequeue方法取得数据

3.取走元素后队列是否为空,如果不为空唤醒其他等待中的队列

复制代码

 1 public E take() throws InterruptedException { 2         E x; 3         int c = -1; 4         final AtomicInteger count = this.count; 5         final ReentrantLock takeLock = this.takeLock; 6         takeLock.lockInterruptibly(); 7         try { 8             while (count.get() == 0) { 9                 notEmpty.await();10             }11             x = dequeue();12             c = count.getAndDecrement();13             if (c > 1)14                 notEmpty.signal();15         } finally {16             takeLock.unlock();17         }18         if (c == capacity)19             signalNotFull();20         return x;21     }

复制代码

enqueue()和dequeue()方法实现都比较简单,无非就是将元素添加到队尾,从队顶取走元素,感兴趣的朋友可以自己去看一下,这里就不粘贴了。

 

LinkedBlockingQueue与LinkedBlockingDeque比较

 

LinkedBlockingDeque和LinkedBlockingQueue的相同点在于: 
1. 基于链表 
2. 容量可选,不设置的话,就是Int的最大值

和LinkedBlockingQueue的不同点在于: 
1. 双端链表和单链表 
2. 不存在哨兵节点 
3. 一把锁+两个条件

实例:

 小记:AtomicInteger的getAndIncrment和getAndDcrement()等方法,这些方法分为两步,get和increment(decrement),在get和increment中间可能有其他线程进入,导致多个线程get到的数值是相同的,也会导致多个线程累加后的值其实累加1.在这种情况下,使用volatile也是没有效果的,因为get之后没有对值进行修改,不能触发volatile的效果。

复制代码

 1 public class ProducerAndConsumer { 2     public static void main(String[] args){ 3  4         try{ 5             BlockingQueue queue = new LinkedBlockingQueue(5); 6  7             ExecutorService executor = Executors.newFixedThreadPool(5); 8             Produer producer = new Produer(queue); 9             for(int i=0;i<3;i++){10                 executor.execute(producer);11             }12             executor.execute(new Consumer(queue));13 14             executor.shutdown();15         }catch (Exception e){16             e.printStackTrace();17         }18 19     }20 }21 22 class Produer implements  Runnable{23 24     private BlockingQueue queue;25     private int nums = 20;  //循环次数26 27     //标记数据编号28     private static volatile AtomicInteger count = new AtomicInteger();29     private boolean isRunning = true;30     public Produer(){}31 32     public Produer(BlockingQueue queue){33         this.queue = queue;34     }35 36     public void run() {37         String data = null;38         try{39             System.out.println("开始生产数据");40             System.out.println("-----------------------");41 42           while(nums>0){43                 nums--;44                 count.decrementAndGet();45 46                 Thread.sleep(500);47                 System.out.println(Thread.currentThread().getId()+ " :生产者生产了一个数据");48                 queue.put(count.getAndIncrement());49             }50         }catch(Exception e){51             e.printStackTrace();52             Thread.currentThread().interrupt();53         }finally{54             System.out.println("生产者线程退出!");55         }56     }57 }58 59 class Consumer implements Runnable{60 61     private BlockingQueue queue;62     private int nums = 20;63     private boolean isRunning = true;64 65     public Consumer(){}66 67     public Consumer(BlockingQueue queue){68         this.queue = queue;69     }70 71     public void run() {72 73         System.out.println("消费者开始消费");74         System.out.println("-------------------------");75 76         while(nums>0){77             nums--;78             try{79                 while(isRunning){80                     int data = (Integer)queue.take();81                     Thread.sleep(500);82                     System.out.println("消费者消费的数据是" + data);83             }84 85             }catch(Exception e){86                 e.printStackTrace();87                 Thread.currentThread().interrupt();88             }finally {89                 System.out.println("消费者线程退出!");90             }91 92         }93     }94 }

复制代码

效果:

复制代码

 1 12 :生产者生产了一个数据 2 11 :生产者生产了一个数据 3 13 :生产者生产了一个数据 4 12 :生产者生产了一个数据 5 消费者消费的数据是-3 6 11 :生产者生产了一个数据 7 13 :生产者生产了一个数据 8 12 :生产者生产了一个数据 9 消费者消费的数据是-310 13 :生产者生产了一个数据11 11 :生产者生产了一个数据12 12 :生产者生产了一个数据13 消费者消费的数据是-314 13 :生产者生产了一个数据15 11 :生产者生产了一个数据16 消费者消费的数据是-317 消费者消费的数据是-3

复制代码

可以看到,有多个producer在生产数据的时候get到的是相同的值。

原文出处:https://www.cnblogs.com/duodushuduokanbao/p/9556555.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消