zoukankan      html  css  js  c++  java
  • 多线程-阻塞队列

    什么是阻塞队列?

      一个支持两个附加操作的队列。这两个附加操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者场景。


     非阻塞队列与阻塞队列处理方法对比:

      非阻塞队列中几个主要方法有:

        add(E e):将元素e插入到队列末尾。成功返回true,失败(队列已满)抛出异常。

        remove(e):移除队首元素,成功返回true,失败(队列为空)抛出异常。

        offer(E e):将元素e插入到队列末尾,成功返回true,失败(队列已满)返回false。

        poll():移除并获取队首元素,成功返回队首元素,失败返回null。

        peek():获取队首元素,成功返回队首元素,失败返回null。

      一般建议在使用非阻塞队列时,使用offer、poll、peek。因为这三个方法能通过返回值判断操作成功与否。非阻塞队列中的方法都没有进进行同步措施。

      阻塞队列包括了非阻塞队列中的大部分方法,上面五个都存在其中。但在阻塞队列中都进行了同步措施。除此之外阻塞队列中还有几个非常有用的方法:

        put(E e):向队尾存入元素,队列满则等待。

        take():获取队首元素,队列为空则等待。

        offer():向队尾存入元素,队列满则等待一定时间,到达时间期限时,如果还未插入成功则返回false,否则返回true。

        poll():获取队首元素,队列为空则等待一定时间,到达时间期限时,如果未取到则返回null,否则返回取得的元素。


    Java中提供的7个阻塞队列:  

      ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

      LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。默认和最大长度为Integer.MAX_VALUE。按照先进先出的原则对元素进行排序。

      PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。默认下元素采取自然顺序排序,也可通过比较器comparator来指定元素的排序规则,按升序排序。

      DelayQueue:一个使用优先级队列实现的无界阻塞队列。

      SynchronousQueue:一个不存储元素的阻塞队列。每一个put操作必须等到一个take操作。否则不能继续添加元素。适用于传递性场景。

      LinkedTransferQueue:一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列LinkedTransferQueue多了tryTransfer和transfer方法。

      LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。


     ArrayBlockingQueue:

      一个数组实现的有界阻塞队列。按先进先出(FIFO)原则对元素进行排序。默认不保证访问者公平的访问队列。

    公平访问队列:
        阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素

       我们可以ArrayBlockingQueue建立一个公平的阻塞队列,其通过可重入锁实现访问者的公平性

    ArrayBlockingQueue fairQueue = new  ArrayBlockingQueue(1000,true);

     DelayQueue:

      支持延时获取元素的无界阻塞队列。使用PriorityQueue来实现。队列中的元素必须实现Delayed接口。创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

    DelayQueue常见运用的应用场景:
        1、缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了
        2、定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的

     LinkedTransferQueue:

      transfer方法:

        如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。

      tryTransfer方法:

        试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。对于带有时间限制的tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。


     LinkedBlockingDeque:

      可以从队列两端插入和移出元素。双端队列因其多了一个操作队列的入口。多线程同时入队时,减少了一半的竞争。相比其他队列,LinkedBlockingDeque多了addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast等方法,以First单词结尾的方法,表示插入,获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另外插入方法add等同于addLast,移除方法remove等效于removeFirst。但是take方法却等同于takeFirst,不知道是不是Jdk的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化LinkedBlockingDeque时可以初始化队列的容量,用来防止其再扩容时过渡膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中。


     阻塞队列的实现原理:

      我们采用ArrayBlockingQueue为例。先看一下其中的几个成员变量

     //用以存储元素的实际是一个数组
    final Object[] items; 
    //队首元素下标
    int takeIndex;
    //队尾元素下标
    int putIndex;
    //队列中元素个数
    int count;
     //可重入锁
    final ReentrantLock lock;
    //等待条件
    private final Condition notEmpty;
    private final Condition notFull;

       构造器:

    //capacity指定容量
    public ArrayBlockingQueue(int capacity) {}
    //指定容量和公平性
    public ArrayBlockingQueue(int capacity, boolean fair) {}
    //指定容量、公平性及用另一个集合进行初始化
    public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
    }

      看一下put和take方法的源码,了解一下实现:

    public void put(E e) throws InterruptedException {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
         //获取可中断锁 lock.lockInterruptibly();
    try {
           //当前元素个数等于数组长度
    while (count == items.length)
              //等于则让线程等待 notFull.await();
            //不等于则存入元素 enqueue(e); }
    finally { lock.unlock(); } } //checkNotNull 校验元素是否为null,是抛出异常 private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); } //enqueue 存入元素 private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++;
         //存入成功后,唤醒取元素线程 notEmpty.signal(); }

       take方法实现:

    public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
          //获取可中断锁 lock.lockInterruptibly();
    try {
            //当元素个数为0
    while (count == 0)
              //暂停线程 notEmpty.await();
            //不为0,获取元素
    return dequeue(); } finally { lock.unlock(); } } private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued();
          //获取成功,唤醒插入线程 notFull.signal();
    return x; }

      take方法与put方法实现相似,put方法等待的是notFull信号,而take方法等待的是notEmpty信息。阻塞队列与我们利用wait/notify和非阻塞队列实现生产者-消费者思路类似。

      阻塞队列常用场景是socket客户端数据读取和解析。读取数据的线程不断将数据放入队列。解析线程不断从队列取数据解析。


     Demo:利用BlockingQueue实现生产者、消费者模型

    /**
     * @Title: Resource
     * @Description: 资源池
     * @date 2019/1/1810:31
     */
    public class Resource {
        private static final Logger logger = LoggerFactory.getLogger(com.imooc.demo.thread.Resource.class);
        private BlockingQueue blockingQueue = new LinkedBlockingDeque(10);
        /**
         * 取资源
         */
        public synchronized void remove(){
            try {
                blockingQueue.take();
                logger.info("消费者" + Thread.currentThread().getName() + "消耗一件资源," + "当前资源池有" + blockingQueue.size()+ "个资源");
            } catch (InterruptedException e) {
                logger.error("remove error {}",e);
            }
        }
    
        /**
         * 添加资源
         */
        public synchronized void add(){
            try {
                blockingQueue.put(1);
                logger.info("生产者" + Thread.currentThread().getName()+ "生产一件资源," + "当前资源池有" + blockingQueue.size() + "个资源");
            } catch (InterruptedException e) {
                logger.error("add error {}",e);
            }
        }
    }
    
    /**
     * @Title: ConsumerThread
     * @Description: 消费者
     * @date 2019/1/1810:07
     */
    public class ConsumerThread extends Thread{
        private static final Logger logger = LoggerFactory.getLogger(ConsumerThread.class);
        private Resource resource;
        public ConsumerThread(Resource resource){
            this.resource = resource;
        }
        public void run(){
            while (true){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    logger.error("ConsumerThread error {}",e);
                }
                resource.remove();
            }
        }
    }
    
    /**
     * @Title: ProducerThread
     * @Description: 生产者
     * @date 2019/1/1810:08
     */
    public class ProducerThread extends Thread{
        private static final Logger logger = LoggerFactory.getLogger(ProducerThread.class);
        private Resource resource;
        public ProducerThread(Resource resource){
            this.resource = resource;
        }
        public void run(){
            while (true){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    logger.error("ProducerThread error {}",e);
                }
                resource.add();
            }
        }
    }
    
    /**
     * @Title: Client
     * @Description: 客户端
     * @date 2019/1/1810:10
     */
    public class Client {
        public static void main(String[] args) {
            Resource resource =new Resource();
            ProducerThread pt1 = new ProducerThread(resource);
    
            ConsumerThread ct1 = new ConsumerThread(resource);
            ConsumerThread ct2 = new ConsumerThread(resource);
            pt1.start();
            ct1.start();
            ct2.start();
        }
    }
  • 相关阅读:
    Poj 3713 Transferring Sylla 3-连通
    SPOJ 7758 Growing Strings AC自动机DP
    ural 1209. 1,10,100,1000.....
    ural 1197. Lonesome Knight
    ural 1149. Sinus Dances
    优先级队列
    Codeforces Round #384 (Div. 2) C. Vladik and fractions
    Codeforces Round #384 (Div. 2) B. Chloe and the sequence
    Codeforces Round #384 (Div. 2) A. Vladik and flights
    POJ 1246 Find The Multiple
  • 原文地址:https://www.cnblogs.com/zhangbLearn/p/9951824.html
Copyright © 2011-2022 走看看