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. 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;
- 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也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
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。
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;
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; }