JDK Source code Analysis--arrayblockingqueue and Linkedblockingqueue

Source: Internet
Author: User
Tags assert

Objective: This paper compares Arrayblockingqueue and linkedblockingqueue by analyzing the JDK source code, so that it can be used flexibly later.

1. In the Java concurrent package, a blocking queue Blockingqueue is added for multithreaded programming. The core methods of Blockingqueue are:

Boolean Add (e e), adding E to the blockingqueue. Returns true if the Blockingqueue can be accommodated, otherwise throws an exception.
Boolean offer (E), which indicates that if possible, add E to Blockingqueue, which returns true if Blockingqueue can be accommodated, otherwise false.
void put (e e), add E to Blockingqueue, if Blockqueue has no space, the thread calling this method is blocked until Blockingqueue has space to continue.
E Poll (long timeout, timeunit unit), take the first object in the Blockingqueue, if not immediately remove, you can wait for the time specified by the parameters, and return NULL when not taken.
E take () takes the first object in the Blockingqueue, and if Blockingqueue is empty, the thread calling this method is blocked until Blockingqueue has new data to join.
int Drainto (COLLECTION<? super e> C) and int drainto (COLLECTION<? super e> C, int maxelements), disposable from Blockingqueu E gets all available data objects (you can also specify the number of data to be fetched), which improves the efficiency of data acquisition and does not require multiple batches of locks or release locks.

Note: Blockingqueue does not accept null elements. Some implementations throw NullPointerException when attempting to add, put, or offer a null element. Null is used as a warning value indicating that the poll operation failed.

2. Four implementation classes commonly used by Blockingqueue

Arrayblockingqueue: The specified size of the Blockingqueue, its constructor must take an int parameter to indicate its size. The objects it contains are sorted in FIFO (first in, first out) order.
2) Linkedblockingqueue: The size of the blockingqueue, if its constructor with a specified size parameter, the resulting blockingqueue has a size limit, if not with the size parameter, The size of the generated blockingqueue is determined by Integer.max_value. The objects it contains are sorted in FIFO (first in, first out) order
3) Priorityblockingqueue: Similar to Linkedblockqueue, but the sort of objects it contains is not FIFO, but is determined by the natural sort order of the object or the comparator of the constructor.
4) Synchronousqueue: Special Blockingqueue, the operation of which must be put and take the alternate completion.

This article will analyze and compare arrayblockingqueue and linkedblockingqueue from the JDK source level

3. Arrayblockingqueue Source Analysis

Arrayblockingqueue is a bounded blocking queue supported by an array. This queue sorts elements by FIFO (first-in, in-out) principle. The head of the queue is the element that has the longest time in the queue, and the tail of the queue is the element with the shortest time in the queue. The new element is inserted at the end of the queue, and the queue retrieval operation starts from the head of the queue to get the element.
This is a typical "bounded buffer", in which the fixed-size array keeps the elements inserted by the producer and the user extracts the elements. Once such a buffer is created, it is no longer possible to increase its capacity. Attempting to put an element into the full queue causes the put operation to be blocked, and attempting to retrieve an element from an empty queue will result in a similar blocking.

Arrayblockingqueue is created with a specified capacity capacity (the maximum number of elements that can be stored because it does not automatically expand). One of the construction methods is:

public arrayblockingqueue (int capacity, Boolean fair) {        if (capacity <= 0)            throw new IllegalArgumentException ();        This.items = (e[]) new object[capacity];        lock = new Reentrantlock (fair);        Notempty = Lock.newcondition ();        Notfull =  lock.newcondition ();    }

The variables defined in the Arrayblockingqueue class are:

    /** the queued Items  */    private final e[] items;    /** Items index for next take, poll or remove */    private int takeindex;    /** Items index for next put, offer, or add. */    private int putindex;    /** number of items in the queue */    private int count;    /     * * Concurrency control uses the classic two-condition algorithm     * found in any textbook.     */    /** Main Lock Guarding All Access */    private final reentrantlock lock;    /** Condition for Waiting takes */    private final Condition notempty;    /** Condition for waiting puts */    private final Condition notfull;
Use the array items to store elements, because they are circular queues, using Takeindex and Putindex to mark the position of put and take. As you can see, only one lock reentrantlock is defined in the class, defining two Condition objects: Notemputy and Notfull, respectively, to control the take and put operations. Note: This article focuses on put () and take () operations, similar to other methods.

The source code for the put (e-E) method is as follows. Before a put operation, you must obtain a lock and lock the operation to ensure thread safety. After locking, if the queue is found to be full, the notfull.await () method is called, such as the current thread is caught waiting. The Notfull.signal () method is called to activate the thread until another thread has took an element. After activation, continue with the following insert operation.

/**     * Inserts the specified element at the tail of this queue, waiting     * for space to become available if the Queu E is full.     *     *    /public void put (e e) throws Interruptedexception {//cannot hold a null  element        if (E = = null) throw new Nullpointer Exception ();        Final e[] items = this.items;//Array Queue        final reentrantlock lock = this.lock;//locking        lock.lockinterruptibly ();        try {            try {///when the queue is full, call the Notfull.await () method to cause the thread to block. The Notfull.signal () method is called to activate the thread until an element is removed.                while (count = = items.length)                    notfull.await ();            } catch (Interruptedexception IE) {                Notfull.signal (); Propagate to non-interrupted thread                throw ie;            } Insert element e into the end of the queue            Insert (e);        } finally {//unlock            lock.unlock ();}    }
Insert (e) method is as follows:

    /**     * Inserts element at current put position, advances, and signals.     * Call only when holding lock.     *    /private void Insert (E x) {        Items[putindex] = x;  Subscript plus 1 or equal to 0        putindex = Inc (PUTINDEX);        ++count;  Count Plus 1//if a take () thread falls into a block, the action activates the takes () thread and proceeds to the element operation. If no take () thread is stuck, the operation is meaningless.        notempty.signal ();    } /**     * circularly increment I.     *    /FINAL int Inc (int i) {//Here you can see the use of the circular queue        return (++i = = items.length)? 0:i;    }
The Take () method code is as follows. The take operation is the opposite of the put operation and is not described in detail.

Public E take () throws Interruptedexception {        final reentrantlock lock = This.lock;        Lock.lockinterruptibly ();  Locking        try {            try {///When the queue is empty, call the Notempty.await () method to make the thread block. The Notempty.signal () method is called to activate the thread until an element is removed.                while (count = = 0)                    notempty.await (),            } catch (Interruptedexception IE) {                notempty.signal (); Propagate to non-interrupted thread                throw ie;            } Remove the team head element            E x = Extract ();            return x;        } finally {            lock.unlock ();//Unlock        }    }
The extract () method is as follows:

/**     * Extracts element at the current take position, advances, and signals.     * Call only when holding lock.     *    /Private E Extract () {        final e[] items = this.items;        E x = Items[takeindex];        Items[takeindex] = null;        Takeindex = Inc (TAKEINDEX);        --count;        Notfull.signal ();        return x;    }
Summary: Put and take operations, sharing the same lock object. That is, put and take cannot be executed in parallel!
4. Linkedblockingqueue Source Analysis

A linked list -based blocking queue, similar to Arraylistblockingqueue, maintains a data buffer queue (which is made up of a list of linked lists), and when a producer puts a data into the queue, the queue fetches the data from the producer. and is cached inside the queue, and the producer returns immediately; the producer queue is blocked until the queue buffer reaches the maximum cache capacity (Linkedblockingqueue can be specified by the constructor) until the consumer consumes a piece of data from the queue, and the producer thread is awakened. On the contrary, the consumer side of the processing is based on the same principle. While Linkedblockingqueue is able to efficiently handle concurrency data, it also uses separate locks for both producer and consumer to control data synchronization, which means that producers and consumers can operate the data in the queue in parallel with high concurrency, This improves the concurrency performance of the entire queue.
As a developer, it is important to note that if you construct a Linkedblockingqueue object without specifying its capacity size, Linkedblockingqueue will default to a capacity (Integer.max_value) that is like an infinite size, In this case, if the producer's speed is greater than the consumer's speed, perhaps not until the queue is full of congestion, the system memory may have been exhausted.

The variables defined in the Linkedblockingqueue class are:

    /** the capacity bound, or integer.max_value if none */    private final int capacity;    /** Current number of elements *    /private final Atomicinteger count = new Atomicinteger (0);    /** Head of Linked list */    private transient node<e> Head;    /** Tail of linked list */    private transient node<e> last    ; /** Lock held by Take, poll, etc *    /private final Reentrantlock Takelock = new Reentrantlock ();    /** Wait queue for waiting takes *    /private final Condition Notempty = Takelock.newcondition ();    /** Lock held by put, offer, etc *    /private final Reentrantlock Putlock = new Reentrantlock ();    /** Wait queue for waiting puts *    /private final Condition Notfull = Putlock.newcondition ();
Two reentrantlock locks are defined in this class: Putlock and Takelock, respectively, for both put and take ends. That is, both the build and the consumer have a lock on their own, avoiding the case of competing locks when read (take) is written (put).

/** * Inserts the specified element at the tail of this queue, waiting if * necessary in space to become availabl        E. */public void put (e e) throws interruptedexception {if (E = = null) throw new NullPointerException (); Note:convention in all put/take/etc are to preset local var//holding count negative to indicate failure        Unless set.        int c =-1;        Final Reentrantlock putlock = This.putlock;        Final Atomicinteger count = This.count; Putlock.lockinterruptibly ();             Add Putlock Lock try {/* * Note that count is used in wait guard even though it is * Not protected by lock. This works because count can * is decrease at the this point (all other puts is shut * out by Lock) , and we (or some other waiting put) is * signalled if it ever changes from * capacity.             Similarly for all and uses of count in * other wait guards. *When the queue is full, call the Notfull.await () method to release the lock and get caught in a wait state. There are two cases when the thread is activated//first, after a put thread has added elements, the discovery queue is empty, the call to the Notfull.signal () method activates the blocking thread//second, the take thread takes the element, and the discovery queue is full.            The Notfull.signal () method is also called after the element is removed to activate the blocking thread while (count.get () = = capacity) {notfull.await ();            }//add element e to the queue (end of team) Enqueue (e);                c = count.getandincrement ();//Discovery queue is not full, call notfull.signal () to activate blocked put thread (possibly present) if (C + 1 < capacity)        Notfull.signal ();        } finally {Putlock.unlock ();    if (c = = 0)//The queue is empty, indicating that a take thread has been blocked, call Signalnotempty to activate the blocked take thread signalnotempty (); }
Enqueue (e E) The following method:

    /**     * Creates a node and links it at end of queue.     * @param x the item     *    /private void Enqueue (E x) {        //Assert Putlock.isheldbycurrentthread ();        last = Last.next = new node<e> (x);    }
The Take () method code is as follows. The take operation is the opposite of the put operation and is not described in detail.
Public E take () throws Interruptedexception {        e x;        int c =-1;        Final Atomicinteger count = This.count;        Final Reentrantlock takelock = This.takelock;        Takelock.lockinterruptibly ();        try {                while (count.get () = = 0) {                    notempty.await ();                }            x = Dequeue ();            c = count.getanddecrement ();            if (C > 1)                notempty.signal ();        } finally {            takelock.unlock ();        }        if (c = = capacity)            signalnotfull ();        return x;    }
The Dequeue () method is as follows:

    /**     * Removes a node from head of the queue.     * @return the node     *    /private E dequeue () {        //Assert Takelock.isheldbycurrentthread ();        Node<e> h = head;        node<e> first = H.next;        H.next = h; Help GC        head = first;        E x = First.item;        First.item = null;        return x;    }

Summary: Take and put operations each have a lock, can be read in parallel.


Reference Address:

1). Java Multithreading-Tool article-blockingqueue:http://blog.csdn.net/xiaoliang_xie/article/details/6887115

2). Java Multithreading (five) Blockingqueue in-depth analysis: http://blog.csdn.net/vernonzheng/article/details/8247564

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.