java集合之ArrayBlockingQueue

ArrayBlockingQueue属于BlockingQueue分支下的一个子类,他是一个阻塞队列,通过名字可知,它底层是采用的数组结构来存放数据,既然是数组,那么长度就是固定的,且它是一个阻塞队列,那么就会涉及到一些锁相关的内容,我们从具体的方法中去了解具体的实现。
在这里插入图片描述

1、重要属性
	// 数组对象,也就是存放数据的地方
	final Object[] items;
	// 下一个获取/删除/查看的元素位置
    int takeIndex;
	// 下一个添加元素的位置
    int putIndex;
	// 队列中的元素个数
    int count;
    // 阻塞队列锁(基于Aqs实现,在多线程与高并发专题有介绍)
    final ReentrantLock lock;
	// 读线程排队队列
    private final Condition notEmpty;
	// 写线程排队队列
    private final Condition notFull;
    transient Itrs itrs = null;
2、构造器
	/**
	* 带参构造
	* capacity 容量,代表队列可存放的元素个数,其实就是底层数组的长度items
	*/
    public ArrayBlockingQueue(int capacity) {
    	// 调用自己的构造方法 false代表默认使用非公平锁
        this(capacity, false);
    }
    /**
    * 带参构造
	* capacity 容量,代表队列可存放的元素个数,其实就是底层数组的长度items
	* fair true-公平锁 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();
    }
    /**
    * 带参构造
	* capacity 容量,代表队列可存放的元素个数,其实就是底层数组的长度items
	* fair true-公平锁 false-非公平锁
	* c 需要加入到队列的集合
	*/
   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 {
       // 下面的for循环就是将集合中的元素放入数组
           int i = 0;
           try {
               for (E e : c) {
                   checkNotNull(e);
                   items[i++] = e;
               }
           } catch (ArrayIndexOutOfBoundsException ex) {
               throw new IllegalArgumentException();
           }
           count = i;
           // 这个数组,模仿了一个循环的过程,比如数组长度为3,当放满了之后,putIndex又会从0开始放,
           // 当然是要数据被消费之后
           putIndex = (i == capacity) ? 0 : i;
       } finally {
       	   // 释放锁
           lock.unlock();
       }
    }
3、主要方法
3.1、add(e) 往队列中添加元素
    public boolean add(E e) {
    	// 最终调用的是offer方法
        return super.add(e);
    }
    public boolean offer(E e) {
     checkNotNull(e);
     // 加锁
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
     	 // 如果count(元素个数)已经等于数组长度,代表没有空间,直接返回false,
     	 // 这里判断就可以限制住putIndex的覆盖(当putIndex走完一圈回到了0,
     	 // 但是数组中的元素没有被消费,就不能继续放元素)
         if (count == items.length)
             return false;
         else {
         // 还有空间则进入enqueue方法
             enqueue(e);
             return true;
         }
     } finally {
         lock.unlock();
     }
    }
    private void enqueue(E x) {
        final Object[] items = this.items;
        // 将x放在putIndex位置
        items[putIndex] = x;
        // 对putIndex进行++操作,如果putIndex到了数组长度,则回到0,从头开始添加
        if (++putIndex == items.length)
            putIndex = 0;
        // 元素个数加1
        count++;
        // 唤醒在队列中等待的读线程(消费者)
        notEmpty.signal();
    }
3.2、offer(e)

王队列中放元素的方法,在上面的add中已经介绍过,如果队列已满,则直接返回false,否则就抢锁,成功后往队列添加元素

3.3、put(e)
  public void put(E e) throws InterruptedException {
       checkNotNull(e);
       // 加锁
       final ReentrantLock lock = this.lock;
       lock.lockInterruptibly();
       try {
       	   // 判断队列是否满了,如果满了,那么线程则进入notFull排队等待,
       	   // 那么这里有个问题,为什么要用while,用if进行判断行不行,答案是不行,
       	   // 会产生虚假唤醒的问题,比如,此时C(读线程),A(写线程),B(写线程),在队列中排队,C线程完成工作后,队列
       	   // 有了空间,他就将notFull中等待的写线程进行唤醒,假设唤醒的是A线程,此时A线程并不是直接获得锁,而是从等待
       	   // 队列进入了AQS队列,有了抢锁的资格,但是此时E(写线程)获得锁进来了,发现有空间,将数据放进去了,当E线程
       	   // 是否锁资源后A线程获得了锁,如果不进行长度判断,直接进行写,则会将未被消费的数据覆盖   
           while (count == items.length)
           	   // 没有空间则挂起,等待唤醒
               notFull.await();
           enqueue(e);
       } finally {
           // 释放锁
           lock.unlock();
       }
    }
    private void enqueue(E x) {
        final Object[] items = this.items;
        // 将x放在putIndex位置
        items[putIndex] = x;
        // 对putIndex进行++操作,如果putIndex到了数组长度,则回到0,从头开始添加
        if (++putIndex == items.length)
            putIndex = 0;
        // 元素个数加1
        count++;
        // 唤醒在队列中等待的读线程(消费者)
        notEmpty.signal();
    }
3.3、offer(e,time,unit)

此方法在加入元素时,如果没有空间,会等待timeout(时间),过后才返回成功或者失败,等待过程中被中断,则抛出中断异常

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    checkNotNull(e);
    // 将时间转换为纳秒 为了更精确
    long nanos = unit.toNanos(timeout);
    // 加锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
    	// 判断队列是否满了,如果满了,那么线程则进入notFull排队等待,此次的while与put同理
        while (count == items.length) {
        	// 时间还足够,如果不够则直接返回,如果够,则等待
            if (nanos <= 0)
                return false;
            // 进入等待队列,等待相应的时长
            nanos = notFull.awaitNanos(nanos);
        }
        // 进行入队操作
        enqueue(e);
        return true;
    } finally {
    	// 释放锁
        lock.unlock();
    }
}
 private void enqueue(E x) {
        final Object[] items = this.items;
        // 将x放在putIndex位置
        items[putIndex] = x;
        // 对putIndex进行++操作,如果putIndex到了数组长度,则回到0,从头开始添加
        if (++putIndex == items.length)
            putIndex = 0;
        // 元素个数加1
        count++;
        // 唤醒在队列中等待的读线程(消费者)
        notEmpty.signal();
    }
3.4、remove(Object o)
public boolean remove(Object o) {
  if (o == null) return false;
    final Object[] items = this.items;
    // 加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	// 判断元素个数
        if (count > 0) {
        	// 下一个元素的存放位置
            final int putIndex = this.putIndex;
            // 下一个获取的元素位置
            int i = takeIndex;
            do {
            	// 如果o与i位置的元素匹配上
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                // 没匹配上,则继续找,找到数组尾巴了,就转圈回去从头找
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
            // i如果等于了putIndex,说明所有元素循环结束,没有找到o
        }
        return false;
    } finally {
        lock.unlock();
    }
}
void removeAt(final int removeIndex) {
  final Object[] items = this.items;
  // 如果要删除的元素正好是下一个要获取的元素,直接修改takeIndex就完成了
  if (removeIndex == takeIndex) {
      // 将takeIndex位置的元素置空
      items[takeIndex] = null;
      // 那么下一个要获取的元素位置也相应的要变化
      if (++takeIndex == items.length)
          takeIndex = 0;
      // 元素个数减1
      count--;
      if (itrs != null)
          itrs.elementDequeued();
  } else {
  	  // 否则的话,则要对数组元素进行移动 
      final int putIndex = this.putIndex;
      // for循环则是在移动元素
      for (int i = removeIndex;;) {
          int next = i + 1;
          if (next == items.length)
              next = 0;
          if (next != putIndex) {
              items[i] = items[next];
              i = next;
          } else {
              items[i] = null;
              this.putIndex = i;
              break;
          }
      }
      // 元素个数减1
      count--;
      if (itrs != null)
          itrs.removedAt(removeIndex);
  }
  // 删除元素后,队列就有了空间,唤醒等待的写线程
  notFull.signal();
}
3.5、poll()

获取并删除队列头元素,队列为空则返回null

 public E poll() {
 	 // 加锁
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
     	 // 队列为空则返回null,否则调用dequeue
         return (count == 0) ? null : dequeue();
     } finally {
         // 释放锁
         lock.unlock();
     }
 }
 private E dequeue() {
   final Object[] items = this.items;
   // 取出takeIndex位置的元素
   E x = (E) items[takeIndex];
   // 将该位置置空
   items[takeIndex] = null;
   // 重置takeIndex的值
   if (++takeIndex == items.length)
       takeIndex = 0;
   // 元素个数减1
   count--;
   if (itrs != null)
       itrs.elementDequeued();
   // 唤醒等待的写线程
   notFull.signal();
   // 返回元素
   return x;
}
3.6、take()

此方法获取不到元素,则一直死等

public E take() throws InterruptedException {
	 // 加锁
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly();
     try {
     	 // 没有元素 此处的while与上面的put的while有异曲同工之妙
         while (count == 0)
         	 // 进入等待队列挂起
             notEmpty.await();
         // 队列不为空 则进行出队操作
         return dequeue();
     } finally {
     	 // 释放锁
         lock.unlock();
     }
 }
 private E dequeue() {
   final Object[] items = this.items;
   // 取出takeIndex位置的元素
   E x = (E) items[takeIndex];
   // 将该位置置空
   items[takeIndex] = null;
   // 重置takeIndex的值
   if (++takeIndex == items.length)
       takeIndex = 0;
   // 元素个数减1
   count--;
   if (itrs != null)
       itrs.elementDequeued();
   // 唤醒等待的写线程
   notFull.signal();
   // 返回元素
   return x;
}
3.7、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) {
         	 // 时间还够则等待,没有时间了,那就返回null
             if (nanos <= 0)
                 return null;
             // 此方法返回的是当前线程的剩余时间
             nanos = notEmpty.awaitNanos(nanos);
         }
         // 获取元素(出队操作)
         return dequeue();
     } finally {
     	 // 释放锁
         lock.unlock();
     }
 }
  private E dequeue() {
   final Object[] items = this.items;
   // 取出takeIndex位置的元素
   E x = (E) items[takeIndex];
   // 将该位置置空
   items[takeIndex] = null;
   // 重置takeIndex的值
   if (++takeIndex == items.length)
       takeIndex = 0;
   // 元素个数减1
   count--;
   if (itrs != null)
       itrs.elementDequeued();
   // 唤醒等待的写线程
   notFull.signal();
   // 返回元素
   return x;
}
3.8、peek()

此方法查看队头元素,但不删除,队列为空则返回空

 public E peek() {
 	// 加锁
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
     	// 返回takeIndex位置的元素
         return itemAt(takeIndex); 
     } finally {
     	// 释放锁
         lock.unlock();
     }
 }
 final E itemAt(int i) {
    return (E) items[i];
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值