• 学习BlockingQueue之ArrayBlockingQueue实现原理


    一:对列的基本概念

      1:对列   队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行 删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受 限制的线性表。

    进行插入操作的端称为队尾,进行删除操作的端称为队头。 在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。 因为队列只允许在一端插入,在另一端删除,

    所以只有最早进入队列的元素才能 最先从队列中删除,故队列又称为先进先出(FIFO—firstinfirstout)线性表

      

           2:阻塞对列

    a)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程, 直到队列不满。

    b)支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队 列变为非空。 

    二:常见的阻塞对列类型

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

    ·LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

    ·PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

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

    ·SynchronousQueue:一个不存储元素的阻塞队列。

    ·LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

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

    以上的阻塞队列都实现了 BlockingQueue 接口,也都是线程安全的。

    三:实现方式

    1:ArrayBlockingQueue 

      ArrayBlockingQueue 是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对 元素进行排序。默认情况下不保证线程公平的访问队列,所谓公平访问队列是指 阻塞的线程,

    可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访 问队列的资格,

    有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置。

    示例代码:

    public class AnalysisBlockingQueue {
        public static void main(String[] args) {
            BlockingQueue blockingQueue = new ArrayBlockingQueue(1000000);
            new Thread(new MyProducer(blockingQueue),"producer-thread").start();
            new Thread(new MyConsumer(blockingQueue),"consumer-thread").start();
        }
    }
    
    class MyProducer implements Runnable{
    
        private BlockingQueue blockingQueue;
    
        MyProducer(BlockingQueue blockingQueue){
            this.blockingQueue = blockingQueue;
        }
    
        @Override public void run() {
            for(int i=0;i<100;i++){
                System.out.println(Thread.currentThread()+"正在生产第 "+i+" 个数据");
                blockingQueue.offer(i);
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class MyConsumer implements Runnable{
    
        private BlockingQueue blockingQueue;
    
        MyConsumer(BlockingQueue blockingQueue){
            this.blockingQueue = blockingQueue;
        }
    
        @Override public void run() {
            while(true){
                Object obj= blockingQueue.poll();
                System.out.println("正在消费数据 "+obj);
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }

    执行结果:

    来看一下ArrayBlockingQueue这个对列的源码

     

     这个构造函数,支持设置容量,对列对于线程并发来说默认是非公平的

    当然支持配置,也可以配置为公平的,无论对于生产者还是消费者,多个生产者线程(无论是先阻塞的还是后来的线程,抢占

    对列资源是不公平的,后来的线程也可以先把数据放到对列中)

    还支持将集合中的元素提前放入对列中

    ArrayBlockingQueue只使用了一把锁,生产者和消费者在同一时刻只能进行一个操作,

    在构造的时候对这个锁进行初始化 ,公平和非公平也是通过这把锁控制的

    对列常用的方法总结:

    我们先来看一下用的比较多的offer和poll:

    offer代码:

    首先检查放进来的元素是否为null,如果为null,则抛异常,然后在操作的时候将这个对列上锁,这时其他的线程,包括

    消费者线程和生产者线程都会阻塞,因为它是全部的锁,所以效率应该很低。

    如果对列中元素的数量等于数组的容量,放不下了,返回false,否则调用enqueue向对列放入数据。

     public boolean offer(E e) {
            checkNotNull(e);
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if (count == items.length)
                    return false;
                else {
                    enqueue(e);
                    return true;
                }
            } finally {
                lock.unlock();
            }
        }
    

      

    接下来看一下enqueue入队方法:

    将元素放入数组中,如果此时放入的元素是数组的最后一个元素,那么下一次要从第一个开始放,

    因为消费者取数据的时候是从数组低下标开始取的。对列维持的总数量count++,然后通知阻塞的消费线程

    可以消费数据。

     private void enqueue(E x) {
            // assert lock.getHoldCount() == 1;
            // assert items[putIndex] == null;
            final Object[] items = this.items;
            items[putIndex] = x;
            if (++putIndex == items.length)
                putIndex = 0;
            count++;
            notEmpty.signal();
        }
    

      

    再来看一下取数据的poll方法:

    先上锁,进行操作,如果对列元素数量为0则返回null,否则调用dequeue取元素

    public E poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return (count == 0) ? null : dequeue();
            } finally {
                lock.unlock();
            }
        }
    

      

    在看一下dequeue方法:

    从数组中取出对应下标的元素,然后将该下标指向null,如果取数据的下标到达数组的最后一个元素,则下次从

    0下标开始取,对列数量count--,然后通知阻塞的生产者可以向对列放数据了。

     private E dequeue() {
            // assert lock.getHoldCount() == 1;
            // assert items[takeIndex] != null;
            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;
        }
    

      

    这里面有个peek方法,取出数组中的下一个元素,但是不从对列中移除

     public E peek() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                return itemAt(takeIndex); // null when queue is empty
            } finally {
                lock.unlock();
            }
        }
    

      

    再来看一下 add remove  和element方法:

    add直接调用父类的add方法,其实现依赖于offer方法,

    上面我们分析offer放成功,返回true,失败返回false,add在offer基础上又包装了一下,成功返回true,失败抛异常。

     

    remove方法:这个remove不一定是移除对列头部的元素,可以移除对列中的任何元素

     

    如果传进来要删除的元素为null,或者对列为空,或者没有在对列中找到该元素,那么返回false,

    删除成功,则返回true

     再来看一下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();
            }
        }
    

      

     take方法:

    如果对列是空的,那么消费者线程阻塞,否则,元素出队。

       public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                while (count == 0)
                    notEmpty.await();
                return dequeue();
            } finally {
                lock.unlock();
            }
        }
    

      

     总结ArrayBlockingQueue:是一个底层数组结构的对列,生产者和消费者线程由同一个锁控制,生产和消费效率低。

    可以配置显示锁公平还是非公平。

    下一节看一下LinkedBlockingQueue对列原理

  • 相关阅读:
    SELECT IDENT_CURRENT(tableName)和自增长列的纠结
    [置顶]c# 设计模式(1)一 创建型
    我们互联网生活因家庭服务器改变
    互联网创业不妨先放下平台梦
    影响未来的应用ifttt,互联网自主神经系统的又一个有力证据
    什么是ifttt,ifttt怎么玩? ifttt操作体验具体步骤
    杰出企业家的20个好习惯
    折叠分组表格中重用Cell导致的问题
    使用AChartEngine画折线图
    MSSQL获取当前插入的ID号及在高并发的时候处理方式
  • 原文地址:https://www.cnblogs.com/warrior4236/p/12516437.html
Copyright © 2020-2023  润新知