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

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

注意:在阅读本文之前或在阅读的过程中,需要用到ReentrantLock,内容见《第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()》《第六章 ReentrantLock源码解析2--释放锁unlock()》《第七章 ReentrantLock总结

1、对于ArrayBlockingQueue需要掌握以下几点

  • 创建

  • 入队(添加元素)

  • 出队(删除元素)

2、创建

  • public ArrayBlockingQueue(int capacity, boolean fair)

  • public ArrayBlockingQueue(int capacity)

使用方法:

  • Queue<String> abq = new ArrayBlockingQueue<String>(2);

  • Queue<String> abq = new ArrayBlockingQueue<String>(2,true);

通过使用方法,可以看出ArrayBlockingQueue支持ReentrantLock的公平锁模式与非公平锁模式,对于这两种模式,查看本文开头的文章即可。

源代码如下:

    private final E[] items;//底层数据结构
    private int takeIndex;//用来为下一个take/poll/remove的索引(出队)
    private int putIndex;//用来为下一个put/offer/add的索引(入队)
    private int count;//队列中元素的个数     /*
     * Concurrency control uses the classic two-condition algorithm found in any
     * textbook.
     */     /** Main lock guarding all access */
    private final ReentrantLock lock;//锁
    /** Condition for waiting takes */
    private final Condition notEmpty;//等待出队的条件
    /** Condition for waiting puts */
    private final Condition notFull;//等待入队的条件
    /**
     * 创造一个队列,指定队列容量,指定模式
     * @param fair
     * true:先来的线程先操作
     * false:顺序随机
     */
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = (E[]) new Object[capacity];//初始化类变量数组items
        lock = new ReentrantLock(fair);//初始化类变量锁lock
        notEmpty = lock.newCondition();//初始化类变量notEmpty Condition
        notFull = lock.newCondition();//初始化类变量notFull Condition
    }     /**
     * 创造一个队列,指定队列容量,默认模式为非公平模式
     * @param capacity <1会抛异常
     */
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

注意:

  • ArrayBlockingQueue的组成:一个对象数组+1把锁ReentrantLock+2个条件Condition

  • 在查看源码的过程中,也要模仿带条件锁的使用,这个双条件锁模式是很经典的模式

3、入队

3.1、public boolean offer(E e)

原理:

  • 在队尾插入一个元素, 如果队列没满,立即返回true; 如果队列满了,立即返回false

使用方法:

  • abq.offer("hello1");

源代码:

    /**
     * 在队尾插入一个元素,
     * 如果队列没满,立即返回true;
     * 如果队列满了,立即返回false
     * 注意:该方法通常优于add(),因为add()失败直接抛异常
     */
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)//数组满了
                return false;
            else {//数组没满
                insert(e);//插入一个元素
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
    private void insert(E x) {
        items[putIndex] = x;//插入元素
        putIndex = inc(putIndex);//putIndex+1
        ++count;//元素数量+1
        /**
         * 唤醒一个线程
         * 如果有任意一个线程正在等待这个条件,那么选中其中的一个区唤醒。
         * 在从等待状态被唤醒之前,被选中的线程必须重新获得锁
         */
        notEmpty.signal();
    }
    /**
     * i+1,数组下标+1
     */
    final int inc(int i) {
        return (++i == items.length) ? 0 : i;
    }

代码非常简单,流程看注释即可,只有一点注意点:

  • 在插入元素结束后,唤醒等待notEmpty条件(即获取元素)的线程,可以发现这类似于生产者-消费者模式

3.2、public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException

原理:

  • 在队尾插入一个元素,,如果数组已满,则进入等待,直到出现以下三种情况:

    • 被唤醒

    • 等待时间超时

    • 当前线程被中断

使用方法:

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

源代码:

    /**
     * 在队尾插入一个元素,
     * 如果数组已满,则进入等待,直到出现以下三种情况:
     * 1、被唤醒
     * 2、等待时间超时
     * 3、当前线程被中断
     */
    public boolean offer(E e, long timeout, TimeUnit unit)
            throws InterruptedException {         if (e == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);//将超时时间转换为纳秒
        final ReentrantLock lock = this.lock;
        /*
         * lockInterruptibly():
         * 1、 在当前线程没有被中断的情况下获取锁。
         * 2、如果获取成功,方法结束。
         * 3、如果锁无法获取,当前线程被阻塞,直到下面情况发生:
         * 1)当前线程(被唤醒后)成功获取锁
         * 2)当前线程被其他线程中断
         * 
         * lock()
         * 获取锁,如果锁无法获取,当前线程被阻塞,直到锁可以获取并获取成功为止。
         */
        lock.lockInterruptibly();//加可中断的锁
        try {
            for (;;) {
                if (count != items.length) {//队列未满
                    insert(e);
                    return true;
                }
                if (nanos <= 0)//已超时
                    return false;
                try {
                    /*
                     * 进行等待:
                     * 在这个过程中可能发生三件事:
                     * 1、被唤醒-->继续当前这个for(;;)循环
                     * 2、超时-->继续当前这个for(;;)循环
                     * 3、被中断-->之后直接执行catch部分的代码
                     */
                    nanos = notFull.awaitNanos(nanos);//进行等待(在此过程中,时间会流失,在此过程中,线程也可能被唤醒)
                } catch (InterruptedException ie) {//在等待的过程中线程被中断
                    notFull.signal(); // 唤醒其他未被中断的线程
                    throw ie;
                }
            }
        } finally {
            lock.unlock();
        }
    }

注意:

  • awaitNanos(nanos)是AQS中的一个方法,这里就不详细说了,有兴趣的自己去查看AQS的源代码。

  • lockInterruptibly()与lock()的区别见注释

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

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

相关文章:
【推荐】 事务消息中心-TMC
【推荐】 3分钟带你了解负载均衡服务
【推荐】 Kudu vs HBase

ArrayBlockingQueue源码解析(1)的更多相关文章

  1. 第八章 ArrayBlockingQueue源码解析

    注意:在阅读本文之前或在阅读的过程中,需要用到ReentrantLock,内容见<第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()><第六章 Reen ...

  2. Java并发包源码学习系列:阻塞队列实现之ArrayBlockingQueue源码解析

    目录 ArrayBlockingQueue概述 类图结构及重要字段 构造器 出队和入队操作 入队enqueue 出队dequeue 阻塞式操作 E take() 阻塞式获取 void put(E e) ...

  3. ArrayBlockingQueue源码解析(2)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3.3.public void put(E e) throws InterruptedException 原 ...

  4. Java源码解析——集合框架(二)——ArrayBlockingQueue

    ArrayBlockingQueue源码解析 ArrayBlockingQueue是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection ...

  5. Java并发包源码学习系列:阻塞队列实现之LinkedBlockingQueue源码解析

    目录 LinkedBlockingQueue概述 类图结构及重要字段 构造器 出队和入队操作 入队enqueue 出队dequeue 阻塞式操作 E take() 阻塞式获取 void put(E e ...

  6. Java并发包源码学习系列:阻塞队列实现之PriorityBlockingQueue源码解析

    目录 PriorityBlockingQueue概述 类图结构及重要字段 什么是二叉堆 堆的基本操作 向上调整void up(int u) 向下调整void down(int u) 构造器 扩容方法t ...

  7. Java并发包源码学习系列:阻塞队列实现之DelayQueue源码解析

    目录 DelayQueue概述 类图及重要字段 Delayed接口 Delayed元素案例 构造器 put take first = null 有什么用 总结 参考阅读 系列传送门: Java并发包源 ...

  8. Java并发包源码学习系列:阻塞队列实现之SynchronousQueue源码解析

    目录 SynchronousQueue概述 使用案例 类图结构 put与take方法 void put(E e) E take() Transfer 公平模式TransferQueue QNode t ...

  9. Java并发包源码学习系列:阻塞队列实现之LinkedTransferQueue源码解析

    目录 LinkedTransferQueue概述 TransferQueue 类图结构及重要字段 Node节点 前置:xfer方法的定义 队列操作三大类 插入元素put.add.offer 获取元素t ...

随机推荐

  1. java 泛型: 通配符? 和 指定类型 T

    1. T通常用于类后面和 方法修饰符(返回值前面)后面 ,所以在使用之前必须确定类型,即新建实例时要制定具体类型, 而?通配符通常用于变量 ,在使用时给定即可 ? extends A  :  通配符上 ...

  2. 基于centos6.5 hadoop 伪分布式安装

    步骤1:修改IP 地址和主机名: vi /etc/sysconfig/network-scripts/ifcfg-eth0 如果该文件打开为空白文件代表你计算机上的网卡文件不是这个名称“ifcfg-e ...

  3. 安装配置nfs

    #yum -y install nfs-utils rpcbind #service rpcbind start#service nfs start #chkconfig --add rpcbind# ...

  4. git学习笔记:一台电脑上配置两个git账户

    如何在一台电脑上配置两个git账户,现在云端仓库很多,有开源中国的 gitee.com 微软的 github.com 还有 gitlab.com 和 bitbucket.org 等等,下面是具体步骤 ...

  5. 746. Min Cost Climbing Stairs

    两种方法,核心思想都一样,求出走到每一步上的最小开销,直到最后一步和倒数第二步,比较其最小值返回即可. 方法一,用一个辅助的容器 class Solution { public: int minCos ...

  6. Github上下载某一个文件夹

    1.安装svn sudo apt-get install subversion 2.修改下载文件夹的链接 例如,https://github.com/a***b/learn/tree/master/m ...

  7. hadoop学习笔记-目录

    以下是hadoop学习笔记的顺序: hadoop学习笔记(一):概念和组成 hadoop学习笔记(二):centos7三节点安装hadoop2.7.0 hadoop学习笔记(三):hdfs体系结构和读 ...

  8. C++之输出100-200内的素数

    素数(质数)    除了1和它本身以外不再被其他的除数整除. // 输出100--200内的素数 #include<iostream> using namespace std; int m ...

  9. HDU 5663 Hillan and the girl (莫比乌斯反演 + 分块)

    题意:给定n,m,求,其中F(x)=0,,如果x是完全平方数,否则是1. 析: 由于按照题意的F,不好筛选,所以我们反过来,F(x),x是平方数,就是1,否则是0. 这个是可以预处理出来的,可以用筛选 ...

  10. create table b1 as select * from b建表锁表测试

    A: create table a1 like a; insert into a1 as select * from a; B: create table b1 as select * from b; ...