• ArrayBlockingQueue源码分析-Java8


    ArrayBlockingQueue原理介绍

      ArrayBlockingQueue,是基于数组的阻塞队列,队列中的元素按照FIFO顺序。

      创建ArrayBlockingQueue,是需要制定队列的容量的(不可省);指定队列容量后,会一次性创建capacity个长度的数组,用来存放队列元素;

      需要注意的是,ArrayBlockingQueue使用的是循环数组来实现队列,也就是说,有takeIndex指向下一个出队元素,当takeIndex指向了capacity-1的位置(最后一个位置),那么元素出队后,下一次takeIndex会归零;同样的,对于putIndex指向存放下一个入队元素的位置,当putIndex指向了capacity-1的位置(最后一个位置),入队后,putIndex会归零,指向第一个节点。

      另外,基于链表实现的阻塞队列LinkedBlockingQueue,可以参考:https://www.cnblogs.com/-beyond/p/14407364.html

      原文地址:https://www.cnblogs.com/-beyond/p/14407201.html

    属性字段

    /**
     * 序列化版本id
     */
    private static final long serialVersionUID = -817911632652898426L;
    
    /**
     * 保存队列元素的数组
     */
    final Object[] items;
    
    /**
     * 指向下一个元素的位置(进行take、poll、peek和remove),可以理解为队首指针
     */
    int takeIndex;
    
    /**
     * 指向下一个元素的位置(进行put、offer、add),可以理解为队尾指针
     */
    int putIndex;
    
    /**
     * 记录队列中中元素的位置
     */
    int count;
    
    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */
    
    /**
     * 访问控制的锁
     */
    final ReentrantLock lock;
    
    /**
     * 用于take阻塞的condition
     */
    private final Condition notEmpty;
    
    /**
     * 用于put阻塞的condition
     */
    private final Condition notFull;
    
    /**
     * Shared state for currently active iterators, or null if there
     * are known not to be any.  Allows queue operations to update
     * iterator state.
     */
    transient Itrs itrs = null;
    

      

    构造方法

    ArrayBlockingQueue提供了3个构造方法

    /**
     * 初始化ArrayBlockingQueue,设置队列大小,使用非公平策略(默认)
     */
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
    
    /**
     * 初始化ArrayBlockingQueue,设置队列大小,并设置是否使用公平策略
     */
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0) {
            throw new IllegalArgumentException();
        }
    
        // 创建n个元素的数组(赋值给队列数组)
        this.items = new Object[capacity];
    
        // 创建可重复锁(使用设置的公平策略)
        lock = new ReentrantLock(fair);
    
        // 创建lock关联的notEmpty和notFull condition
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }
    
    /**
     * 初始化ArrayBlockingQueue,设置队列大小,并设置是否使用公平策略
     * 然后将传入的元素加入到队列中(按照传入元素的顺序),元素不能为null
     */
    public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
        // 初始化队列
        this(capacity, fair);
    
        // 获取锁,并加锁
        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            int i = 0;
            try {
                // 遍历集合,将集合的元素顺序加入到队列数组中
                for (E e : c) {
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
    
            // 修改队列元素的数量
            count = i;
    
            // 修改putIndex,也就是下一个元素放入的下标,如果队列满了(i为capacity),那么下一个位置为0(队首)
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    

      

    添加元素(入队)

    ArrayBlockingQueue的入队分两大类,阻塞式入队和非阻塞式入队;

    阻塞式入队是指,如果入队失败(队列已满),则会阻塞,知道成功入队,才会结束阻塞。

    非阻塞式入队是指,不管入队是否成功,都会立即返回。

    非阻塞式添加元素

    非阻塞式添加元素,是指尝试元素入队时,不管是否成功,都立即返回;

    对应的是add和offer两个方法,add调用的其实是offer。

    /**
     * 将元素插入队列的尾部(入队)
     * 入队成功,返回true;入队失败,返回false
     */
    public boolean offer(E e) {
        // 判断元素是否为null,如果为null,则抛出NPE
        checkNotNull(e);
    
        // 获取锁,并加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 判断队列是否已满
            if (count == items.length) {
                // 队列已满,返回false
                return false;
            } else {
                // 队列未满,调用enqueue来入队,并返回true(入队成功)
                enqueue(e);
                return true;
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    

     

    /**
     * 向队列中添加元素(放入队尾),返回添加元素是否成功
     * 如果队列已满,那么将会抛出IllegalStateException异常
     * 如果添加的元素为null,则会抛出NullPointerException异常
     */
    public boolean add(E e) {
        return super.add(e);//调用AbstractQueue的add方法
    }
    
    // 这是AbstractQueue的add方法,ArrayBlockingQueue重写了offer方法
    public boolean add(E e) {
        // 调用的ArrayBlockingQueue的offer方法
        if (offer(e)) { 
            return true;
        } else {
            throw new IllegalStateException("Queue full");
        }
    }
    

      

    阻塞式添加元素

    /**
     * 元素入队,如果队列已满,则会阻塞直到元素入队完成
     */
    public void put(E e) throws InterruptedException {
        // 判断元素是否为null,如果为null,则抛出NPE
        checkNotNull(e);
    
        // 获取锁,并加锁(可中断)
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 自旋锁,如果队列已满,则线程进行阻塞
            while (count == items.length) {
                // 阻塞,等待notFull的signal
                notFull.await();
            }
    
            // 队列未满,进行入队操作
            enqueue(e);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    

      除了上面一直阻塞,ArrayBlockingQueue还提供了超时时长的入栈接口

    /**
     * 元素入队,并设置超时时长
     */
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        // 判断元素是否为null,如果为null,则抛出NPE
        checkNotNull(e);
    
        // 时间转换为纳秒
        long nanos = unit.toNanos(timeout);
    
        // 加锁(可中断)
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 如果队列已满,并且已经超时,则返回false(入队失败)
            while (count == items.length) {
                if (nanos <= 0) {
                    return false;
                }
    
                // 阻塞,返回剩余的超时时长
                nanos = notFull.awaitNanos(nanos);
            }
    
            // 队列未满,进行入队操作,并返回true(入队成功)
            enqueue(e);
            return true;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    

      

    入队操作

    上面的offer、add、put操作中,真正执行入队操作的是enqueue方法,如下:

    /**
     * 将元素插入putIndex所指的位置
     */
    private void enqueue(E x) {
        // 获取队列数组
        final Object[] items = this.items;
    
        // 将元素放入数组元素中(队尾)
        items[putIndex] = x;
    
        // 如果队列已满,那么重置putIndex(因为是循环数组实现队列,所以下一个放入的位置为队首)
        if (++putIndex == items.length) {
            putIndex = 0;
        }
    
        // 元素数量加1
        count++;
    
        // 唤醒notEmpty(通知队列不为空,有元素)
        notEmpty.signal();
    }

      注意,enqueue中的几个点,入队、队列size+1,唤醒notEmpty,这些操作都是在ReentrantLock加锁成功后执行的(offer方法中),所以不存在并发问题。

    获取元素

    这里的获取元素,是指调用peek接口,peek方法并不会将元素出队,而是只返回队首元素。

    /**
     * 获取元素(不出队),如果队列为空,则返回null
     */
    public E peek() {
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 获取队列指定位置的元素(takeIndex,也就是下一个出队的元素位置)
            return itemAt(takeIndex);
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 获取队列数组中的i位置上的元素
     */
    @SuppressWarnings("unchecked")
    final E itemAt(int i) {
        return (E) items[i];
    }

    元素出队

    与元素入队相似,出队也分阻塞式和非阻塞式;

    非阻塞式出队

    /**
     * 非阻塞式出队
     *
     * @return 出队的元素(如果队列为空,则返回null)
     */
    public E poll() {
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 如果队列为空,则返回null,否则进行出队操作
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
    

      

    阻塞式出队

    /**
     * 出队,设置超时时间
     */
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        // 加锁
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 如果队列为空,并且未超时,则进行阻塞
            while (count == 0) {
                // 如果已经超时,则返回null
                if (nanos <= 0) {
                    return null;
                }
                
                // 线程阻塞,等待notEmpty的signal
                nanos = notEmpty.awaitNanos(nanos);
            }
            
            // 队列不为空,则进行出队
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
    

      

    /**
     * 出队,阻塞式
     */
    public E take() throws InterruptedException {
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 如果队列为空,则进行阻塞,等待notEmpty.signal
            while (count == 0) {
                notEmpty.await();
            }
            
            // 进行出队
            return dequeue();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    

      

    出队操作

    上面的poll、take中,真正执行出队操作的是dequeue方法,如下:

    /**
     * 出队操作
     */
    private E dequeue() {
        // 获取队列数组
        final Object[] items = this.items;
    
        // 获取队列数组中队首的元素
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
    
        // 将队首元素设置为null(释放)
        items[takeIndex] = null;
    
        // 如果takeIndex达到数组长度,那么就重设队首指针
        if (++takeIndex == items.length) {
            takeIndex = 0;
        }
    
        // 队列元素减1
        count--;
    
        // 如果itrs不为null,则调用元素出队接口(elementDequeue)
        if (itrs != null) {
            itrs.elementDequeued();
        }
    
        // notFull唤醒(表示可以继续入队元素)
        notFull.signal();
    
        // 返回出队的元素
        return x;
    }
    

      

    获取队列元素数量

    /**
     * 获取队列元素数量
     * 注意,获取size也加锁了,这是因为避免有其他线程正在出队或者入队,导致获取的size不正确
     * 加了锁,能够保证获取size时,队列没有被修改
     */
    public int size() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
    

      

      原文地址:https://www.cnblogs.com/-beyond/p/14407201.html 

    如需转载,请注明文章出处,谢谢!!!
  • 相关阅读:
    致亲爱的304
    C语言中简单的for循环和浮点型变量
    C程序内存管理
    变量
    我哭了
    那一场邂逅
    如何修改安卓项目的图标
    Android requires compiler compliance level 5.0 or 6.0. Found '1.4' instead.解决方法
    Android 异步加载
    大家好,第一次用博客记录一些东西
  • 原文地址:https://www.cnblogs.com/-beyond/p/14407201.html
Copyright © 2020-2023  润新知