BlockingQueue

1.介绍

ArrayBlockingQueue是一个阻塞式的队列,在看jdk内部尤其是一些多线程,大量使用了blockinkQueue 来做的。blockinkQueue  继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。

ArrayBlockingQueue有哪些缺点呢?

a)队列长度固定且必须在初始化时指定,所以使用之前一定要慎重考虑好容量;
b)如果消费速度跟不上入队速度,则会导致提供者线程一直阻塞,且越阻塞越多,非常危险
c)只使用了一个锁来控制入队出队,效率较低,那是不是可以借助分段的思想把入队出队分裂成两个锁呢?

 

常用的操作包括 add ,offer,put,remove,poll,take,peek。前三者add offer put 是插入的操作。后面四个方法是取出的操作。他们之间的区别和关联:

add: 内部实际上获取的offer方法,当Queue已经满了时,抛出一个异常。不会阻塞

offer:当Queue已经满了时,返回false。不会阻塞。

put:当Queue已经满了时,会进入等待,只要不被中断,就会插入数据到队列中。会阻塞,可以响应中断。

取出方法中 remove和add相互对应。也就是说,调用remove方法时,假如队列为空,则抛出异常。另外的,poll与offer相互对应。take和put相互对应。peek方法比较特殊,前三个取出的方法,都会将元素从Queue的头部溢出,但是peek不会,实际上只是,获取队列头的元素。peek方法也不会阻塞。当队列为空时,直接返回Null。

2.对比LinkedBlockingQueue

LinkedBlockingQueue也是一个阻塞式的队列,与ArrayBlockingQueue的区别是什么呢?

LinkedBlockingQueue保存元素的是一个链表。其内部有一个Node的内部类,其中有一个成员变量 Node next。就这样形成了一个链表的结构,要获取下一个元素,只要调用next就可以了。而ArrayBlockingQueue则是一个数组。
LinkedBlockingQueue内部读写(插入获取)各有一个锁,而ArrayBlockingQueue读写共享一个锁

 

3.选择LinkedBlockingQueue还是ArrayBlockingQueue

个人感觉大多数场景适合使用LinkedBlockingQueue。在JDK源码当中有说明,LinkedBlockingQueue比ArrayBlockingQueue有更高的吞吐量,但是性能表现更难预测(也就是说相比ArrayBlockingQueue性能表现不稳定,但是也很稳定了)。

为什么会有吞吐量的区别,个人以为可能是ArrayBlockingQueue两个锁的缘故,在大量并发的情况下,插入和读取都很多时,就会造成一点的时间浪费。

还有一点,应为LinkedBlockingQueue创建时,默认会直接创建一个Integer.MAX_VALUE的数组,当插入少,读取多时,就会造成很大的空间浪费。而LinkedBlockingQueue实际上实在等需要的时候才会创建一个Node节点。

 

 

4.1 保存数据的结构,是一个Object的数组

/** The queued items */
final Object[] items;
 

4.2 下一次取元素的索引位置  poll, peek or remove 

     下一次入队的位置  putIndex put, offer, or add

/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */ 队列中元素数量
int count;

4.2全局锁,这是一个掌管所有访问操作的锁。读写都会使用这个锁

/** Main lock guarding all access */ 
final ReentrantLock lock;

/** Condition for waiting takes */ 出列条件
private final Condition notEmpty;

/** Condition for waiting puts */ 入列条件
private final Condition notFull;
构造器源码 
初始化指定容量的数组,初始化非公平的重入锁,初始化读等待队列,初始化写等待队列。
 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 {
        int i = 0;
        try {
            // 遍历集合
            for (E e : c) {
                // 检查元素是否为空,为空抛出空指针异常
                checkNotNull(e);
                // 存放元素
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        // 初始化数组中元素的个数为计算后的i
        count = i;
        // 插入元素的下标索引,如果i的容量达到了指定的容量
        // 插入元素的下标为0,否则为i
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        // 释放当前线程的锁
        lock.unlock();
    }
}

入队方法(都需要等待获取锁 - > 当获取到锁之后)

ArrayBlockingQueue提供了诸多方法,可以将元素加入队列尾部。

  • add(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException
  • offer(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false
  • offer(E e, long timeout, TimeUnit unit) :将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间
  • put(E e) :将指定的元素插入此队列的尾部,如果该队列已满,则堵塞等待可用的空间, 直到出队dequeue()方法唤醒
 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");
  }

  
  public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock(); // 一直等到获取锁
        try {
            if (count == items.length) //假如当前容纳的元素个数已经等于数组长度,那么返回false
                return false;
            else {
                enqueue(e);   // 将元素插入到队列中,返回true
                return true;
            }
        } finally {
            lock.unlock(); //释放锁
        }
    }



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 {
        // 如果数组满了,就阻塞nanos纳秒
        // 如果唤醒这个线程时依然没有空间且时间到了就返回false
        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) {		
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    //putIndex是下一个放至元素的坐标,添加元素到数组中
    items[putIndex] = x;		
    if (++putIndex == items.length)	//putIndex+1, 并且比较是否与数组长度相同,是的话,则从数组开头
        putIndex = 0;			//插入元素,这就是循环数组的奥秘了
    count++; //元素总量+1
    notEmpty.signal();// 唤醒阻塞出列的线程 (如果队列为空,则进行出列操作是会阻塞)
}

put: 将指定的元素插入此队列的尾部,如果该队列已满,会堵塞等待可用的空间 ,只要不被中断,就会插入数据到队列中

private final Condition notFull;


public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //如果当前的线程没有发生中断,那么就将当前线程锁定, 如果线程中断了抛出异常
    lock.lockInterruptibly();
    try {
        //队列满了,一直阻塞在这里
        while (count == items.length)
            notFull.await();
       //元素入队
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

出队方法

  • poll() :获取并移除此队列的头,如果此队列为空,则返回 null
  • poll(long timeout, TimeUnit unit) :获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)
  • remove(Object o) :从此队列中移除指定元素的单个实例(如果存在)
  • take() :获取并移除此队列的头部,在元素变得可用之前一直堵塞等待(如果有必要),直到入队方法enqueue() 唤醒
  • peek():不会真正的从队列中删除元素,实际上只是取出头元素而已。
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 {
        // 如果队列无元素,则阻塞等待nanos纳秒
        // 如果下一次这个线程获得了锁但队列依然无元素且已超时就返回null
        while (count == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}


 //元素出队
  private E dequeue() {
        final Object[] items = this.items;
        E x = (E) items[takeIndex];
        //将要取出的元素指向null 表示这个元素已经取出去了
        items[takeIndex] = null;
       //takeIndex +1,同样的假如已经取到了数组的末尾,那么就要重头开始取
        if (++takeIndex == items.length)
            takeIndex = 0;  //这就是循环数组
        count--;
        if (itrs != null)
            itrs.elementDequeued(); // 如果迭代器itrs不为null,则需要维护下该迭代器
        //唤醒堵塞的入列线程  (和notFull.await()对应)
        notFull.signal(); 
        return x;
    }
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    //如果当前的线程没有发生中断,那么就将当前线程锁定,如果线程中断了抛出异常
    lock.lockInterruptibly();
    try {
        // /如果数组中的元素数量为0,那么取元素的线程阻塞在此处
        while (count == 0)
           // 将当前线程进行等待(和notEmpty.signal()方法对应)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}
 peek 方法不会真正的从队列中删除元素,实际上只是取出头元素而已。
 /** items index for next take, poll, peek or remove */
    int takeIndex;   

  public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return itemAt(takeIndex); // null when queue is empty
        } finally {
            lock.unlock();
        }
    }

remove   

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 {
                    if (o.equals(items[i])) {
                        removeAt(i);
                        return true;
                    }
                    if (++i == items.length)
                        i = 0;
                } while (i != putIndex);
            }
            //count=0 直接返回false
            return false;
        } finally {
            lock.unlock();
        }
    }
http://cmsblogs.com/?p=2381 
http://cmsblogs.com/?p=4755

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值