Seven kinds of blocking queue

  The queue in front of our contacts are non-blocking queue, such as PriorityQueue, LinkedList (LinkedList is a doubly linked list, which implements the Dequeue Interface).

  When using non-blocking queue there is a big question is this: it will not clog on the current thread, then the consumer in the face of similar - when the producer model, it is necessary to implement additional policies and synchronization between threads wake policy, this it is very cumbersome to implement. But with blocking queue is not the case, it would produce the current thread blocked, such as a thread take elements from an empty blocking queue, in which case the thread will be blocked until the blocking queue with the elements. When there are elements in the queue, blocked thread will automatically wake up (we do not need to write code to wake up). This provides a great convenience.

1. What is blocking queue
   Blocking queue (BlockingQueue) is a support for two additional operations queue. These two additional operational support insertion and removal method of blockage.
  1) supports blocking insertion method: means that when the queue is full, the queue will block the thread insert elements until the queue dissatisfaction.
  2) supports blocking removal method: means that the queue is empty, obtain the thread elements will wait for the queue to become non-empty.
  Blocking queue commonly used in the producers and consumers of the scene, the producer thread element is added to the queue, the consumer is the thread to take elements from the queue.

1. nonblocking queue in several major methods:

  add (E e): the element is inserted into the end of the queue e, if inserted successful, returns true; if the insertion fails (i.e. the queue is full), it will throw an exception;

  remove (): remove the head of the queue elements removed if successful, returns true; if removal failed (the queue is empty), an exception is thrown;

  offer (E e): the element is inserted into the end of the queue e, if inserted successful, returns true; if the insertion fails (i.e. the queue is full), false is returned;

  poll (): remove the team and get the first element, if successful, the head of the queue element is returned; otherwise return null;

  peek (): Gets the first element of the team, if successful, the head of the queue element is returned; otherwise null

  Nonblocking queue, generally recommended to use three methods offer, poll and PEEK, add and remove deprecated methods. Since the use of three methods offer, poll and peek value is determined by the success return operation, the use of add and remove a method can not achieve such results. Note that the method of non-blocking queue are not synchronized measures.

2. The blocking queue in several major methods:

  Blocking queue includes most of the methods of non-blocking queue, five methods listed above are present in the blocking queue, but be aware that these five methods in the blocking queue have been synchronized measures. In addition, blocking queue provides another four very useful method:

  put (E e): put element method is used to deposit the tail, if the queue is full, waiting;

  take (): take the first method is used to take the elements from the team, if the queue is empty, waiting;

  offer (E e, long timeout, TimeUnit unit): offer element method is used to deposit the tail, if the queue is full, then waits for a certain time, when the time limit is reached if it is not successfully inserted, returns false; otherwise return true;

  poll (long timeout, TimeUnit unit): poll method is used to take elements from the first team, if the queue is empty, then wait a certain period of time, when the time limit is reached if to take, or null; otherwise element made;

 
  Blocking queue is used to store elements producers, consumers used to get the container element. When blocking queue is not available, four kinds of treatments provided by these two additional operations, as shown in Table.
  
  • Throws an exception: When the queue is full, if you go down the queue to insert elements, throws IllegalStateException ( "Queuefull") exception. When the queue is empty when retrieving an element from the queue NoSuchElementException will throw an exception.
  • Returns the special value: When an element is inserted into the queue, returns the element is inserted success, returns true if successful, otherwise returns false. If the method is to remove, an element is removed from the queue, it returns the element if no null is returned.
  • It has been blocked: When blocking queue is full, if the producer thread to put queue element, the queue will block until the producer thread until the queue is available or respond to abort. When the queue is empty, if the consumer thread take elements from the queue, the queue will be blocked live consumer thread until the queue is not empty.
  • Exit Timeout: When the blocking queue is full, if the producer thread insert elements into the queue, the queue will block the producer thread for some time, if more than a specified time, the producer thread exits.

 

II. Seven major blocking queue

  Since Java 1.5, offers several blocking queue in java.util.concurrent package, mainly in the following:

  1.ArrayBlockingQueue: Based on the array to achieve a bounded blocking queue that internal maintains a fixed length data buffer queue (the queue consists of an array), the elements of this queue is sorted according to the principle of first in first out (FIFO), and You must specify size when creating capacity ArrayBlockingQueue objects . ArrayBlockingQueue also kept inside two integer variables, respectively identify the location of the queue head and tail in the array.

  And may also specify non fairness and fairness, non-default fair. The so-called fair access to the queue means that the blocked thread, can follow the access queue blocking the order, which is to block the thread to access the queue. Unfairness is a thread to wait for non-fair when the queue is available, the blocked thread can be eligible to compete for access to the queue, it is possible to finally access the blocked thread queue. In order to ensure fairness, generally decreases throughput. We can create a level playing field blocking team use the following code column.

 

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
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();
}

  2.LinkedBlockingQueue:基于链表实现的一个有界阻塞队列,内部维持着一个数据缓冲队列(该队列由链表构成),此队列按照先进先出的原则对元素进行排序。当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(可以通过LinkedBlockingQueue的构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程将会被唤醒,反之对于消费者这端的处理也基于同样的原理。在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已经被消耗殆尽了。

  LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

  3.PriorityBlockingQueue:支持优先级排序的无界阻塞队列,以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,默认情况下元素采取自然顺序排列,也可以通过构造函数传入的Compator对象来决定。并且也是按照优先级顺序出队,每次出队的元素都是优先级最高的元素。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只是在没有可消费的数据时阻塞数据的消费者,因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志)。

  4.DelayQueue:基于PriorityQueue,一种支持延时的获取元素的无界阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

   5.SynchronousQueue:一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。 可以认为SynchronousQueue是一个缓存值为1的阻塞队列,但是SynchronousQueue内部并没有数据缓存空间,数据是在配对的生产者和消费者线程之间直接传递的。可以这样来理解:SynchronousQueue是一个传球手,SynchronousQueue不存储数据元素,队列头元素是第一个排队要插入数据的线程,而不是要交换的数据,SynchronousQueue负责把生产者线程处理的数据直接传递给消费者线程,生产者和消费者互相等待对方,握手,然后一起离开。它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。在创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列。
   6.LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。

  transfer()方法:如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法),transfer()方法可以把生产者传入的元素立刻传输给消费者;如果没有消费者在等待接收元素,transfer()方法会将元素存放到队列的tail节点,并等到该元素被消费者消费了才返回。

  transfer()方法的关键代码如下:

Node pred = tryAppend(s, haveData);
return awaitMatch(s, pred, e, (how == TIMED), nanos);

  第一行代码是试图把存放当前元素的s节点作为tail节点,第二行代码是让CPU自旋等待消费者消费元素。因为自旋会消耗CPU,所以自旋一定的次数后使用Thread.yield()方法来暂停当前正在执行的线程,并执行其他线程。

  tryTransfer()方法:该方法是用来试探生产者传入的元素是否能直接传给消费者,如果没有消费者等待接收元素,则返回false。与transfer()方法的区别:tryTransfer()方法是立即返回(无论消费者是否接收),transfer()方法是必须等到消费者消费了才返回。对于带有时间限制的tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间之后再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。

   7.LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外,插入方法add等同于addLast,移除方法remove等效于removeFirst。
 
三.阻塞队列的实现原理
   本文以ArrayBlockingQueue为例,其他阻塞队列实现原理可能和ArrayBlockingQueue有一些差别,但是大体思路应该类似,有兴趣的朋友可自行查看其他阻塞队列的实现源码。
  首先看一下ArrayBlockingQueue类中的几个成员变量:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    /**
     * Serialization ID. This class relies on default serialization
     * even for the items array, which is default-serialized, even if
     * it is empty. Otherwise it could not be declared final, which is
     * necessary here.
     */
    private static final long serialVersionUID = -817911632652898426L;
    /** The queued items */
    final Object[] items;
    /** 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;
    /** Main lock guarding all access */
    final ReentrantLock lock;
    /** Condition for waiting takes */
    private final Condition notEmpty;
    /** Condition for waiting puts */
    private final Condition notFull;
    transient Itrs itrs = null;

 

  可以看出,ArrayBlockingQueue中用来存储元素的实际上是一个数组,takeIndex和putIndex分别表示队首元素和队尾元素的下标,count表示队列中元素的个数。 lock是一个可重入锁,notEmpty和notFull是等待条件。
  从上述代码中我们可知,如果队列是空的,消费者会一直等待,当生产者添加元素时,生产者是使用Condition线程间通信的方法来通知另一个消费者线程的当生者往列里添加元素会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。那具体是怎么通知的的?我们可详细看分析下下面几个方法。
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();  //释放锁
        }
    }
private void enqueue(E x) {//相当于add()方法
    final Object[] items = this.items;
    items[putIndex] = x;//在队尾添加元素
    if (++putIndex == items.length)//索引自增,如果已是最后一个位置,重新设置 putIndex = 0
     putIndex = 0;
    count++;
    notEmpty.signal();
}
public E take() throws InterruptedException {//由于此时并发容器已满,所以生产者生产失败,释放了锁,轮到消费者执行
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly(); //操作前先上锁
        try {
            while (count == 0)//判断容器不为空
                notEmpty.await();
            return dequeue();//调用该方法
        } finally {
            lock.unlock();
        }
    }
private E dequeue() {//相当于remove()
    final Object[] items = this.items;//获取数组容器
    E x = (E) items[takeIndex];//获取队首元素,因为ArrayBlockingQueue是先进先出队列
    items[takeIndex] = null;//将该位置置空
    if (++takeIndex == items.length)//索引自增,如果已是最后一个位置,重新设置 putIndex = 0
     takeIndex = 0;
    count--;//将容器中元素个数减一
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();//唤醒其他被阻塞的线程,由于刚才生产者因容器已满而被阻塞掉,这时候就会被该线程唤醒了,唤醒之后就可继续它的生产工作。
    return x;
}

 

 
   从put方法的实现可以看出,它先获取了锁,并且获取的是可中断锁,然后判断当前元素个数是否等于数组的长度,如果相等,表示 队列元素已满, 调用notFull.await()进行等待, 那么当前线程将会被notFull条件对象挂起加到等待队列中,直到队列有空位才会唤醒执行添加操作。但如果队列没有满,那么就直接调用enqueue(e)方法将元素加入到数组队列中。调用tack()方法也是同样的原理。
 
 
 
,所 公平 访问队 列是指阻塞的 线 程,可以按照
阻塞的先后 访问队 列,即先阻塞 线 程先 访问队 列。非公平性是 先等待的 线 程是非公平
的,当 列可用 ,阻塞的 线 程都可以争 夺访问队 列的 格,有可能先阻塞的 线 程最后才 访问
列。 了保 公平性,通常会降低吞吐量。我 可以使用以下代 码创 建一个公平的阻塞
列。

 

Guess you like

Origin www.cnblogs.com/ljl150/p/12628300.html