• ArrayBlockingQueue源码解析(2)


    此文已由作者赵计刚授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。


    3.3、public void put(E e) throws InterruptedException

    原理:

    • 在队尾插入一个元素,如果队列满了,一直阻塞,直到数组不满了或者线程被中断

    使用方法:

            try {
                abq.put("hello1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

    源代码:

        /**
         * 在队尾插入一个元素
         * 如果队列满了,一直阻塞,直到数组不满了或者线程被中断
         */
        public void put(E e) throws InterruptedException {
            if (e == null)
                throw new NullPointerException();
            final E[] items = this.items;
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                try {
                    while (count == items.length)//队列满了,一直阻塞在这里
                        /*
                         * 一直等待条件notFull,即被其他线程唤醒
                         * (唤醒其实就是,有线程将一个元素出队了,然后调用notFull.signal()唤醒其他等待这个条件的线程,同时队列也不慢了)
                         */
                        notFull.await();
                } catch (InterruptedException ie) {//如果被中断
                    notFull.signal(); // 唤醒其他等待该条件(notFull,即入队)的线程
                    throw ie;
                }
                insert(e);
            } finally {
                lock.unlock();
            }
        }

     

    4、出队

    4.1、public E poll()

    原理:

    • 如果没有元素,直接返回null;如果有元素,将队头元素置null,但是要注意队头是随时变化的,并非一直是items[0]。

    使用方法:

    abq.poll();

    源代码:

        /**
         * 出队
         */
        public E poll() {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                if (count == 0)//如果没有元素,直接返回null,而非抛出异常
                    return null;
                E x = extract();
                return x;
            } finally {
                lock.unlock();
            }
        }

        /**
         * 出队
         */
        private E extract() {
            final E[] items = this.items;
            E x = items[takeIndex];//获取出队元素
            items[takeIndex] = null;//将出队元素位置置空
            /*
             * 第一次出队的元素takeIndex==0,第二次出队的元素takeIndex==1
             * (注意:这里出队之后,并没有将后面的数组元素向前移)
             */
            takeIndex = inc(takeIndex);
            --count;//数组元素个数-1
            notFull.signal();//数组已经不满了,唤醒其他等待notFull条件的线程
            return x;//返回出队的元素
        }

     

    4.2、public E poll(long timeout, TimeUnit unit) throws InterruptedException

    原理:

    • 从对头删除一个元素,如果数组不空,出队;如果数组已空且已经超时,返回null;如果数组已空且时间未超时,则进入等待,直到出现以下三种情况:

      • 被唤醒

      • 等待时间超时

      • 当前线程被中断

    使用方法:

            try {
                abq.poll(1000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

    源代码:

        /**
         * 从对头删除一个元素,
         * 如果数组不空,出队;
         * 如果数组已空,判断时间是否超时,如果已经超时,返回null
         * 如果数组已空且时间未超时,则进入等待,直到出现以下三种情况:
         * 1、被唤醒
         * 2、等待时间超时
         * 3、当前线程被中断
         */
        public E poll(long timeout, TimeUnit unit) throws InterruptedException {
            long nanos = unit.toNanos(timeout);//将时间转换为纳秒
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                for (;;) {
                    if (count != 0) {//数组不空
                        E x = extract();//出队
                        return x;
                    }
                    if (nanos <= 0)//时间超时
                        return null;
                    try {
                        /*
                         * 进行等待:
                         * 在这个过程中可能发生三件事:
                         * 1、被唤醒-->继续当前这个for(;;)循环
                         * 2、超时-->继续当前这个for(;;)循环
                         * 3、被中断-->之后直接执行catch部分的代码
                         */
                        nanos = notEmpty.awaitNanos(nanos);
                    } catch (InterruptedException ie) {
                        notEmpty.signal(); // propagate to non-interrupted thread
                        throw ie;
                    }
    
                }
            } finally {
                lock.unlock();
            }
        }

     

    4.3、public E take() throws InterruptedException

    原理:

    • 将队头元素出队,如果队列空了,一直阻塞,直到数组不为空或者线程被中断

    使用方法:

            try {
                abq.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

    源代码:

        /**
         * 将队头元素出队
         * 如果队列空了,一直阻塞,直到数组不为空或者线程被中断
         */
        public E take() throws InterruptedException {
            final ReentrantLock lock = this.lock;
            lock.lockInterruptibly();
            try {
                try {
                    while (count == 0)//如果数组为空,一直阻塞在这里
                        /*
                         * 一直等待条件notEmpty,即被其他线程唤醒
                         * (唤醒其实就是,有线程将一个元素入队了,然后调用notEmpty.signal()唤醒其他等待这个条件的线程,同时队列也不空了)
                         */
                        notEmpty.await();
                } catch (InterruptedException ie) {
                    notEmpty.signal(); // propagate to non-interrupted thread
                    throw ie;
                }
                E x = extract();
                return x;
            } finally {
                lock.unlock();
            }
        }

      

    总结:

    1、具体入队与出队的原理图:这里只说一种情况,见下图,途中深色部分表示已有元素,浅色部分没有元素。


     

    上面这种情况是怎么形成的呢?当队列满了,这时候,队头元素为items[0]出队了,就形成上边的这种情况。

    假设现在又要出队了,则现在的队头元素是items[1],出队后就形成下面的情形。


     

    出队后,对头元素就是items[2]了,假设现在有一个元素将要入队,根据inc方法,我们可以得知,他要插入到items[0]去,入队了形成下图:


    以上就是整个入队出队的流程,inc方法上边已经给出,这里再贴一遍:

        /**
         * i+1,数组下标+1
         * 注意:这里这样写的原因。
         */
        final int inc(int i) {
            return (++i == items.length) ? 0 : i;
        }

     

    2、三种入队对比:

    • offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false-->不阻塞

    • put(E e):如果队列满了,一直阻塞,直到数组不满了或者线程被中断-->阻塞

    • offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果数组已满,则进入等待,直到出现以下三种情况:-->阻塞

      • 被唤醒

      • 等待时间超时

      • 当前线程被中断

     

    3、三种出对对比:

    • poll():如果没有元素,直接返回null;如果有元素,出队

    • take():如果队列空了,一直阻塞,直到数组不为空或者线程被中断-->阻塞

    • poll(long timeout, TimeUnit unit):如果数组不空,出队;如果数组已空且已经超时,返回null;如果数组已空且时间未超时,则进入等待,直到出现以下三种情况:

      • 被唤醒

      • 等待时间超时

      • 当前线程被中断


    免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

    更多网易技术、产品、运营经验分享请点击


    相关文章:
    【推荐】 深入解析SQLServer高可用镜像实现原理

  • 相关阅读:
    BZOJ 3924 / Luogu P3345 [ZJOI2015]幻想乡战略游戏 (动态点分治/点分树)
    BZOJ 3065 带插入区间K小值 (替罪羊树套线段树)
    BZOJ 3217: ALOEXT (块状链表套trie)
    BZOJ 3514: Codechef MARCH14 GERALD07加强版 (LCT维护最大生成树+主席树)
    BZOJ 3932: [CQOI2015]任务查询系统 (主席树板题)
    BZOJ 3658: Jabberwocky (双向链表+BIT)
    BZOJ 1180 [CROATIAN 2009]OTOCI // BZOJ 2843 极地旅行社 // Luogu P4321 [COCI 2009] OTOCI / 极地旅行社 (LCA板题)
    BZOJ 2759 一个动态树好题 (LCT)
    BZOJ 2244: [SDOI2011]拦截导弹 (CDQ分治 三维偏序 DP)
    codefroces 612E Square Root of Permutation
  • 原文地址:https://www.cnblogs.com/163yun/p/10141546.html
Copyright © 2020-2023  润新知