• LinkedBlockingQueue源码解析(3)


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

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


    4.3、public E take() throws InterruptedException

    原理:

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

    使用方法:

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

    源代码:

        /**
         * 出队:
         * 如果队列空了,一直阻塞,直到队列不为空或者线程被中断
         */
        public E take() throws InterruptedException {
            E x;
            int c = -1;
            final AtomicInteger count = this.count;//获取队列中的元素总量
            final ReentrantLock takeLock = this.takeLock;
            takeLock.lockInterruptibly();//获取出队锁
            try {
                while (count.get() == 0) {//如果没有元素,一直阻塞
                    /*
                     * 加入等待队列, 一直等待条件notEmpty(即被其他线程唤醒)
                     * (唤醒其实就是,有线程将一个元素入队了,然后调用notEmpty.signal()唤醒其他等待这个条件的线程,同时队列也不空了)
                     */
                    notEmpty.await();
                }
                x = dequeue();//出队
                c = count.getAndDecrement();//元素数量-1
                if (c > 1)
                    notEmpty.signal();
            } finally {
                takeLock.unlock();
            }
            if (c == capacity)
                signalNotFull();
            return x;
        }

     

    总结:

    1、具体入队与出队的原理图

    图中每一个节点前半部分表示封装的数据x,后边的表示指向的下一个引用。

    1.1、初始化



     初始化之后,初始化一个数据为null,且head和last节点都是这个节点。

    1.2、入队两个元素过后



    这个可以根据入队方法enqueue(E x)来看,源代码再贴一遍:

        /**
         * 创建一个节点,并加入链表尾部
         * 
         * @param x
         */
        private void enqueue(E x) {
            /*
             * 封装新节点,并赋给当前的最后一个节点的下一个节点,然后在将这个节点设为最后一个节点
             */
            last = last.next = new Node<E>(x);
        }

    其实这我们就可以发现其实真正意义上出队的头节点是Head节点的下一个节点。(这也就是Node这个内部类中对next的注释,我没有翻译)

    1.3、出队一个元素后



    表面上看,只是将头节点的next指针指向了要删除的x1.next,事实上这样我觉的就完全可以,但是jdk实际上是将原来的head节点删除了,而上边看到的这个head节点,正是刚刚出队的x1节点,只是其值被置空了。

    这一块对应着源代码来看:dequeue()

        /**
         * 从队列头部移除一个节点
         */
        private E dequeue() {
            Node<E> h = head;// 获取头节点:x==null
            Node<E> first = h.next;// 将头节点的下一个节点赋值给first
            h.next = h; // 将当前将要出队的节点置null(为了使其做head节点做准备)
            head = first;// 将当前将要出队的节点作为了头节点
            E x = first.item;// 获取出队节点的值
            first.item = null;// 将出队节点的值置空
            return x;
        }

     

    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;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况:

      • 被唤醒

      • 等待时间超时

      • 当前线程被中断

     

    4、ArrayBlockingQueue与LinkedBlockingQueue对比

    • ArrayBlockingQueue:

      • 一个对象数组+一把锁+两个条件

      • 入队与出队都用同一把锁

      • 在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高

      • 采用了数组,必须指定大小,即容量有限

    • LinkedBlockingQueue:

      • 一个单向链表+两把锁+两个条件

      • 两把锁,一把用于入队,一把用于出队,有效的避免了入队与出队时使用一把锁带来的竞争。

      • 在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多

      • 采用了链表,最大容量为整数最大值,可看做容量无限

     两个疑问:

    • 入队时:c==0是怎样出现的?

    • 出队时:c==capcity是怎样出现的?

    这两个疑问,都是基于对于AtomicInteger的不熟,不明白LinkedBlockingQueue引用的这两个方法(getAndIncrement和getAndDecrement)先返回旧值还是新值,关于AtomicInteger的源码介绍,请查看《第十一章 AtomicInteger源码解析》,具体链接如下:

    http://www.cnblogs.com/java-zhao/p/5140158.html


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

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


    相关文章:
    【推荐】 谈谈Java异常处理这件事儿
    【推荐】 数据库路由中间件MyCat - 背景篇(2)

  • 相关阅读:
    Python--day27--几个内置方法:__repr__()/__str__()/__del__()/__call__()/__getitem__/__setitem/delitem/__new__/__eq__/__hash__
    常见的图片格式的区别
    前端基础-CSS
    前端基础-HTML
    Python函数相关
    Python文件操作
    Python基础数据类型以及对应方法
    Python基础知识
    拿到别人的Django程序如何在本地RUN起来
    Pycharm使用秘籍
  • 原文地址:https://www.cnblogs.com/zyfd/p/10138080.html
Copyright © 2020-2023  润新知