阻塞队列-ArrayBlockingQueue

ArrayBlockingQueue源码分析

基于数组实现队列,使用时必须指定队列长度

成员变量

final Object[] items; //使用数组作为队列存储元素
        
int takeIndex; //将要取数据的下标
int putIndex; //将要存数据的下表,基于这两个变量实现队列的数据结构,保证先进先出
        
int count;//队列长度
final ReentrantLock lock;//锁,保证线程安全

private final Condition notEmpty; //用于消费者挂起、唤醒线程
private final Condition notFull; //用于生产者挂起、唤醒线程

构造方法

为了保证线程安全,ArrayBlockingQueue中使用ReentrantLock锁保证线程安全,可指定公平或非公平锁

  //指定队列长度,默认公平锁
  public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
//指定数组长度,公平锁/非公平锁
  public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
    //指定数组长度,公平锁/非公平锁,存入元素
    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 {
        final Object[] items = this.items;
        int i = 0;
        try {
            for (E e : c)
                items[i++] = Objects.requireNonNull(e);
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
        }

添加元素方法

add(E e)方法

调用了AbstractQueue中的add方法,调用了offer方法,如果添加失败抛出异常,队列已满。

    public boolean add(E e) {
        return super.add(e);
    }
    
    public boolean add(E e) {
            if (offer(e))
                return true;
            else
                throw new IllegalStateException("Queue full");
        }

offer(E e)方法

添加元素成功返回true,否则返回false

 public boolean offer(E e) {
        //校验数据不能为null
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        //加锁添加数据,线程安全
        lock.lock();
        try {
            //如果队列满了,返回false
            if (count == items.length)
                return false;
            else { //否则,添加成功
                //添加数据
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }
    
    private void enqueue(E e) {
        final Object[] items = this.items;
        //存储数据
        items[putIndex] = e;
        //将要取数据的数组下标+1,如果等于长度了,将putIndex置为0。为了满足FIFO,数组满了后下次添加元素应该从0添加元素,0位置的元素一定先被取出,等被取出再添加新元素
        if (++putIndex == items.length) putIndex = 0;
            count++;
            //唤醒消费者线程
            notEmpty.signal();
    }   

offer(E e, long timeout, TimeUnit unit)方法

指定时间阻塞,如果指定时间内没有添加元素成功,返回false

   public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        //校验元素是否为null
        Objects.requireNonNull(e);
        //时间单位转换为纳秒
        long nanos = unit.toNanos(timeout);
        //加锁
        final ReentrantLock lock = this.lock;
        //允许中断线程抛出异常
        lock.lockInterruptibly();
        try {
            //如果元素满了需要挂起线程或者时间到了返回false
            while (count == items.length) {
                if (nanos <= 0L)
                    return false;
                //挂起线程,是否锁资源,返回剩余阻塞时间
                nanos = notFull.awaitNanos(nanos);
            }
            //添加元素
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

put(E e) 方法

一直等,直到存入元素。

   public void put(E e) throws InterruptedException {
        Objects.requireNonNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                //阻塞,直到被唤醒然后重新尝试添加元素
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

移除元素方法

remove()方法

移除元素,如果元素为空,抛出移除,调用的poll方法

    public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
        }

poll()方法

   public E poll() {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //如果队列长度为0,返回空,否则取出数据
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

    private E dequeue() {
        //获取列表
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //根据取数据下标,获取当前下标下元素
        E e = (E) items[takeIndex];
                //将当前下标下元素置为null
                items[takeIndex] = null;
                //取数据下标+1,如果+1后长度等于数组长度,将取数据的下标置为0
                if (++takeIndex == items.length) takeIndex = 0;
                    //数组长度-1
                    count--;
                if (itrs != null)
                    itrs.elementDequeued();
                    //唤醒生产者线程
                    notFull.signal();
                return e;
    }

poll(long timeout, TimeUnit unit)方法

指定等待一段时间,如果队列仍然为空

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) {
                if (nanos <= 0L)
                    //一定时间后仍然没有数据返回null
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

take()方法

如果队列为空,挂起线程直到添加元素成功

   public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                //挂起线程,等待被唤醒
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

虚假唤醒(代码中的判空或者判断队列是否满了为什么使用while循环)

while (count == 0)

  1. 假设A、B、C三个线程
  2. A,B两个线程消费元素,都执行了(count == 0)判断后执行notEmpty.await()方法,挂起线程
  3. 此时C线程添加完元素,A、B线程被唤醒,无论谁先拿到锁去消费数据都需要重新判断count == 0条件
  4. 不然会有一个线程在count的情况下消费数据

while (count == items.length)

  1. 假设A、B、C三个线程
  2. A、B线程执行了(count == items.length)后执行notFull.await()方法,挂起线程
  3. 此时C线程刚消费完一个元素,A、B线程被唤醒,无论谁先拿到锁添加数据都需要重新判断count == items.length条件
  4. 不然A、B会有一个线程在队列满的情况下添加数据,导致数据被覆盖

以上两种情况被成为虚假唤醒,如果不使用while循环会导致数据安全性问题。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值