• BlockingQueue原理


    概念

    BlockingQueue 翻译成中文阻塞队列,顾名思义就是线程使用队列时会阻塞当前线程;

    BlockingQueue 继承了Collection,具有一般集合所具有的数据存取功能

    BlockingQueue 是线程安全的队列,多线程访问时不会出现同一个数据集中的数据被多次取出,或者覆盖存放的事件

    使用场景

    可用于一个快速反馈的消息队列,无消息时阻塞线程让出CPU,有数据存入时通知线程取出数据,取完后继续阻塞,

    比如用户下单后立刻在大屏上显示有客户下单,比较简单的做法是开启一个定时任务,定期扫订单表;或者接入消息中间件,下单时发送消息,大屏服务监听消息;或者借用reddis队列 解决方式有很多种不一一列举

    示例模拟数据的存取 设置队列的容量为1 是为了更好展示 存取的阻塞特性

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(1);
    
        //模拟存入数据线程
        new Thread(()->{
            int i=0;
            while (true){
                try {
                    //每次循环+1
                    i++;
                    queue.put(i);
                    System.out.println("存入数据"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "存入数据线程").start();
    
        //模拟取出数据线程 1秒钟取一个
        new Thread(()->{
            while (true){
                try {
                    //一秒钟取一个数据
                    Thread.sleep(1000);
                    Integer result = queue.take();
                    System.out.println("取出数据"+result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "取数据线程").start();
    
    }
    
    打印结果:
    存入数据1
    取出数据1
    存入数据2
    取出数据2
    存入数据3
    取出数据3
    存入数据4
    取出数据4
    存入数据5
    取出数据5
    存入数据6
    取出数据6
    存入数据7
    取出数据7

    方法示例

    阻塞队列的使用非常简单,基本上和普通集合一样对数据进行存和取

    public static void main(String[] args) throws InterruptedException {
            ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(2);
    
            //存入一个数据 如果队列满了则一直阻塞到有数据取出
            queue.put(1);
            //取出一个数据 如果队列空了则一直阻塞到有数据存入
            queue.take();
    
            //存入一个数据 如果队列满了则阻塞若干时长(示例为10秒),超时则返回offerResult=false
            boolean offerResult = queue.offer(1, 10, TimeUnit.SECONDS);
            //取出一个数据 如果队列空了则阻塞若干时长(示例为10秒),超时则返回pollResult=null
            Integer pollResult = queue.poll(10, TimeUnit.SECONDS);
    }

    源码分析

    1、接口继承结构 

    2、接口代码

    public interface BlockingQueue<E> extends Queue<E> {
        //向队列中添加元素, 若超过给定队列长度抛出异常
        boolean add(E e);
    
        //向队列中添加元素, 若超过给定队列长度抛出异常
        boolean offer(E e);
    
        //向队列中添加元素, 若超过队列长度则等待队列有剩余容量再加入元素
        void put(E e) throws InterruptedException;
    
        //向队列中添加元素, 若超过给定队列长度则等待给定时长
        boolean offer(E e, long timeout, TimeUnit unit)
            throws InterruptedException;
    
        //获取队列头部元素,并从队列头部移除,若队列为空,则阻塞当前获取线程,并等待新元素加入
        E take() throws InterruptedException;
    
        //获取队列头部元素,并从队列头部移除,若队列为空,则阻塞当前获取线程,并等待元素给定时长
        E poll(long timeout, TimeUnit unit)
            throws InterruptedException;
    
        //返回队列剩余容量
        int remainingCapacity();
    
        //移除指定元素
        boolean remove(Object o);
    
        //返回是否存在指定元素
        public boolean contains(Object o);
    
        
        //将队列中的元素全部移除到给定的集合c中
        int drainTo(Collection<? super E> c);
    
        //将队列中的元素全部移除到给定的集合c中(最多不超过maxElements个)
        int drainTo(Collection<? super E> c, int maxElements);
    }

    3、实现类 ArrayBlockingQueue 分析

    public class ArrayBlockingQueue<E> extends AbstractQueue<E>
            implements BlockingQueue<E>, java.io.Serializable {
    
        //数据集 用于存放元素 初始化固定数组长度 不再扩容
        final Object[] items;
    
        //数据集下一次取数据的下标
        //具体操作为 每次take加1 若take+1==items.length即take最后一个元素
        //则takeIndex重置为0 如此往复
        int takeIndex;
    
        //数据集下一次存数据下标
        int putIndex;
    
        //数据集中 存放元素的个数 即items[i]!=null的个数
        int count;
    
        //重入锁 可选公平与非公平 非本文重点
        final ReentrantLock lock;
    
        //Condition 
        //使用流程 1取数据为空(count==0) 则阻塞等待数据集存入数据 执行等待notEmpty.await 
        //         2存数据数据集肯定不为空(count!=0), 则通知取数据线程继续取数据 执行通知notEmpty.signal
        private final Condition notEmpty;
    
        //Condition 
        //使用流程 1存数据数据集存满(count==items.length)则等待消耗后重新存入 执行等待notFull.await 
        //         2取数据后则数据集未满肯定不满(count<items.length) 则通知存入数据 执行通知notFull.signal 
        private final Condition notFull;
    
        //用户维护ArrayBlockingQueue 作为集合的迭代(Iterator)功能
        //调用ArrayBlockingQueue.iterator()是初始化此属性 非本文重点
        transient Itrs itrs = null;
    
    
    
        //--------------------重点方法------------------------
        
    
        /**
         * 从队列中取一个元素
         * @return [description]
         * @throws InterruptedException [description]
         */
        public E take() throws InterruptedException {
            //对操作进行加锁 多线程时轮流取元素
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                //如果队列中没有对象 则阻塞线程等待
                while (count == 0)
                    //重点:等待存数据的线程通知
                    notEmpty.await();
                //代码运行到此处说明count!=null 执行从队列中取元素
                return dequeue();
            } finally {
                lock.unlock();
            }
        }
    
        private E dequeue() {
            final Object[] items = this.items;
            //从数据集数组items 取出下标takeIndex的数据
            @SuppressWarnings("unchecked")
            E x = (E) items[takeIndex];
            //取完数据之后 将数组对应下标应用置为空(GC对象)
            items[takeIndex] = null;
            //takeIndex+1等于数组长度表示当前下标为数组最后一个对象
            //则takeIndex重新归0
            if (++takeIndex == items.length)
                takeIndex = 0;
            //每次取数据 数据总量减1
            count--;
            //迭代器维护操作
            if (itrs != null)
                itrs.elementDequeued();
            //重点:通知存数据的线程 可以执行数据存放
            notFull.signal();
            return x;
        }
    
    
        /**
         * 存入一个数据
         * @param  e                    [description]
         * @throws InterruptedException [description]
         */
        public void put(E e) throws InterruptedException {
            //校验数据非空
            checkNotNull(e);
            //加锁
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                //若数据集数组items满了 则阻塞线程等待
                while (count == items.length)
                    //重点:等待取出数据的线程通知
                    notFull.await();
                //存入数据
                enqueue(e);
            } finally {
                lock.unlock();
            }
        }
    
        private void enqueue(E x) {
            final Object[] items = this.items;
            //存入数据到下标putIndex
            items[putIndex] = x;
            //如果存数据的下标已经到数据最后一个下标 则putIndex重新归0
            if (++putIndex == items.length)
                putIndex = 0;
            //数据总量加1
            count++;
            //重点:存入数据后通知等待取数据的线程
            notEmpty.signal();
        }
    
    
    
    
    }

     总结:

    BlockingQueue 重点关注

    1、阻塞方式

    Condition notFull 和 Condition notEmpty 的使用,存通知取,取通知存;

    从而达到存满阻塞,取完阻塞,存入通知取,取出通知存的功能

    2、存取游标

    takeIndex 和 putIndex的使用,每次取数据takeIndex加1,到了数据末尾则重新回到数组开始下标0,存数据原理相似逐次加1,到末尾归0

    对于LinkedBlockingQueue实现方式则略有不同,链表式集合多线程取数据时只需要排队从头部节点获取,从末尾存数据,有个小优化,创建LinkedBlockingQueue

    时创建一个虚拟头部节点,不做深究

  • 相关阅读:
    数据库范式
    SQL基础
    JAVA流
    response.getWriter()和jsp中的out对象的区别
    JAVA排序(冒泡,直接选择,反转)
    Collections类常用方法
    JAVA集合
    JAVA面向对象(重载,重写 常用的 toString/equals)
    Java面向对象一(封装 继承 多态 类 对象 方法)
    JAVA基础
  • 原文地址:https://www.cnblogs.com/xieyanke/p/13441318.html
Copyright © 2020-2023  润新知