• 阻塞队列之LinkedBlockingQueue


    概述

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

    构造器

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

    1 public LinkedBlockingQueue();   //设置容量为Integer.MAX
    2 
    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 消费者消费的数据是-3
    10 13 :生产者生产了一个数据
    11 11 :生产者生产了一个数据
    12 12 :生产者生产了一个数据
    13 消费者消费的数据是-3
    14 13 :生产者生产了一个数据
    15 11 :生产者生产了一个数据
    16 消费者消费的数据是-3
    17 消费者消费的数据是-3

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

  • 相关阅读:
    php代码中注释的含义
    MySql-count(*)与count(id)与count(字段)之间的执行结果和性能分析
    mysql通配符进行模糊查询
    我的娃,我的宝贝
    Yii 2.0 query模式语法
    Yii 2.0版本调试输出SQL语句
    mysql高效率随机获取n条数据写法
    mysql语句中判断是否包含某字符串的方法
    应该让老婆多休息
    win10常用快捷键总结
  • 原文地址:https://www.cnblogs.com/duodushuduokanbao/p/9556555.html
Copyright © 2020-2023  润新知