Java Concurrency (i)--thread-safe containers (top)

Source: Internet
Author: User
Tags comparable finally block throw exception

The Java thread-safe container consists of two main categories:

    • Vector, Hashtable as well as the wrapper class Collections.synchronizedList and Collections.synchronizedMap ;
    • Java 5.0 introduces a java.util.concurrent package that contains concurrent queues, concurrent HashMap, and copy-on-write containers.

According to the author, the early use of the synchronization container has two main problems: 1) by adding synchronized keyword to achieve synchronization, this coarse-grained locking operation in the Synchronized keyword itself is not fully optimized, low efficiency; 2) the synchronization container is thread-safe, However, in some external composite operations (example: if not added), the client still needs to lock to ensure data security. Therefore, since Java 5.0, concurrent programming favors the use of java.util.concurrent container classes in the package (Doug Lea), this article will also focus on the container classes in the package, mainly including:

    1. blocking queues
    2. Concurrenthashmap
    3. Copy container when writing
One, blocking the queue

In the concurrency environment, the blocking queue is a common data structure, which ensures the efficient and safe transmission of data, which is very convenient for fast building high quality multi-threaded applications, such as MQ's principle is based on blocking queue. java.util.concurrentcontains a rich queue implementation, as shown in the relationship between them:

    • Blockingqueue, Deque (bidirectional queue) inherits from the queue interface;
    • Blockingdeque also inherits from the Blockingqueue, deque interface, providing blocking bidirectional queue properties;
    • Linkedblockingqueue and Linkedblockingdeque respectively realized the Blockingqueue and Blockingdeque interface;
    • Delayqueue realizes the Blockingqueue interface and provides task delay function.
    • Transferqueue is introduced in Java 7 and is used instead of blockingqueue,linkedtransferqueue as its implementation class.

These queues are described in more detail below:

1.1 Blockingqueue and Blockingdeque

The blocking queue (Blockingqueue) is a queue that supports two additional operations. The two additional operations are:

    • When the queue is empty, the thread that gets the element waits for the queue to become non-empty.
    • When the queue is full, the thread that stores the element waits for the queue to be available.

Blocking queues are often used for producer and consumer scenarios, where the producer is the thread that adds elements to the queue, and the consumer is the thread that takes the elements from the queue. The blocking queue is the container where the producer stores the elements, and the consumer only takes the elements from the container.

The blocking queue provides four methods of handling:

method throw exception Returns the special value has been blocked timeout exit
Insert method add (e) offer (e) put (e) offer (e,time,unit)
Remove method remove () poll () take () poll (time,unit)
Check method element () peek () Not available
    • Throw an exception: the IllegalStateException ("Queue full") exception is thrown when the blocking queue is filled and the element is inserted into the queue. When the queue is empty, an Nosuchelementexception exception is thrown when an element is fetched from the queue.
    • Returns a special value that returns True if the Insert method returns success. Remove the method by taking an element from the queue and returning null if none
    • Always blocked: When the blocking queue is full, if the producer thread puts elements into the queue, the queue blocks the producer thread until it gets the data, or the response interrupts the exit. When the queue is empty, the consumer thread tries to take the element from the queue, and the queue blocks the consumer thread until the queue is available.
    • Timeout exit: When the blocking queue is full, the queue blocks the producer thread for a period of time, and if a certain amount of time is exceeded, the producer thread exits.

Blockingdeque on the basis of Blockingqueue, the properties that support bidirectional queues are added. As shown, the Insert and remove methods, compared to Blockingqueue, become XxxFirst , XxxLast methods, corresponding to each end of the queue, either added or removed from the head, or added or removed at the tail.

1.2 Linkedblockingqueue and Linkedblockingdeque

LinkedBlockingQueueis a bounded blocking queue implemented with a linked list. The default and maximum length for this queue is to Integer.MAX_VALUE sort the elements according to the FIFO principle.

First look at the LinkedBlockingQueue domain in the core:

  Static class Node<e> {E item;    Node<e> Next; Node (E x) {item = x;}} Private final int Capacity;private final Atomicinteger count = new Atomicinteger (); transient node<e> head;private tr Ansient node<e> last;private final Reentrantlock takelock = new Reentrantlock ();p rivate final Condition notempty = t Akelock.newcondition ();p rivate final Reentrantlock putlock = new Reentrantlock ();p rivate final Condition notfull = Putloc K.newcondition ();  
    • LinkedBlockingQueueAnd LinkedList Similarly, the storage of elements through static internal classes Node<E> ;
    • capacityRepresents the maximum capacity that the blocking queue can store, which can be manually specified at the time of creation and the default maximum capacity Integer.MAX_VALUE ;
    • countRepresents the number of elements in the current queue, LinkedBlockingQueue the in and out queues use two different lock objects, so both in-queue and out-of-queue involve concurrent modifications to the number of elements, so an atomic action class is used to solve the thread-safety problem of concurrent modifications to the same variable.
    • headAnd last the head and tail of the linked list are respectively represented;
    • takeLockRepresents the lock that the thread acquires when the element is out of the queue, when it is executed, while the thread obtains it, and when the take poll notEmpty queue is empty, the Condition thread that gets the element is waiting;
    • putLockRepresents the lock that the thread acquires when the element is queued, gets it when it is executed, and so on, and when the put offer notFull queue capacity is reached capacity , the thread that Condition joins the element waits.

Second, LinkedBlockingQueue there are three construction methods, respectively, as follows:

  Public Linkedblockingqueue () {this (integer.max_value);}    public linkedblockingqueue (int capacity) {if (capacity <= 0) throw new IllegalArgumentException ();    This.capacity = capacity; last = head = new node<e> (null);}    Public Linkedblockingqueue (collection<. extends e> c) {this (integer.max_value);    Final Reentrantlock putlock = This.putlock; Putlock.lock ();        Never contended, but necessary for visibility try {int n = 0;            For (e e:c) {if (E = = null) throw new NullPointerException ();            if (n = = capacity) throw new IllegalStateException ("Queue full");            Enqueue (New node<e> (E));        ++n;    } count.set (n);    } finally {Putlock.unlock (); }}

The default constructor is called directly LinkedBlockingQueue(int capacity) , LinkedBlockingQueue(int capacity) initializing the first and the end nodes and placing null. LinkedBlockingQueue(Collection<? extends E> c)joins all elements of a collection into the queue at the same time that the queue is initialized.

The final analysis of the next put and take the process, here is focused on: LinkedBlockingQueue How to implement the addition/removal of parallel?

public void put(E e) throws InterruptedException {    if (e == null) throw new NullPointerException();    int c = -1;    Node<E> node = new Node<E>(e);    final ReentrantLock putLock = this.putLock;    final AtomicInteger count = this.count;    putLock.lockInterruptibly();    try {        while (count.get() == capacity) {            notFull.await();        }        enqueue(node);        c = count.getAndIncrement();        if (c + 1 < capacity)            notFull.signal();    } finally {        putLock.unlock();    }    if (c == 0)        signalNotEmpty();}
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 reason put they are put take together is because they are a pair of reciprocal processes:

    • put gets the putlock and the number of elements of the current queue first before inserting the element, and take gets the takelock first before removing the element and the number of elements in the current queue;
    • put you need to determine if the current queue is full, when the front thread waits when it is full, and take needs to determine if the queue is empty. Queue is empty when the front thread waits;
    • put call enqueue insert element at the end of the team and modify the tail pointer, take call Dequeue places the head at the original first position, and resets the data field of primary to NULL, enabling the deletion of the original first pointer and generating a new head and, at the same time, cut off references to the original head node for easy garbage collection.

        private void Enqueue (node<e> node) {last = Last.next = node;} Private E dequeue () {node<e> h = head; node<e> first = H.next;h.next = h; Help gchead = First;
       E x = First.item;first.item = Null;return x;}  
    • Finally, put determines whether the queue is not full and the queue is empty based on Count ; take based on Count Determines whether the triggering queue is not empty and the queue is full.

Back to the question: LinkedBlockingQueue How do I implement Add/remove parallelism?
LinkedBlockingQueueDifferent locks are used when entering the queue and out of the queue, which means that there is no mutex between the operations. in the case of multiple CPUs, can be done at the same time both consumption, and production, to do parallel processing .

Likewise, on LinkedBlockingDeque LinkedBlockingQueue the basis of this, the properties of the bidirectional operation are added. Continue with put and take for example, LinkedBlockingDeque add the putFirst / putLast , takeFirst / takeLast method, respectively for the queue header, the tail to be added and deleted. Unlike the other LinkedBlockingQueue , LinkedBlockingDeque the in queue and out queue no longer use different lock.

final ReentrantLock lock = new ReentrantLock();private final Condition notEmpty = lock.newCondition();private final Condition notFull = lock.newCondition();

Where lock represents the primary lock for read and write, Notempty and notfull still represent the corresponding control thread state condition. Take putFirst and takeFirst for example:

public void putFirst(E e) throws InterruptedException {    if (e == null) throw new NullPointerException();    Node<E> node = new Node<E>(e);    final ReentrantLock lock = this.lock;    lock.lock();    try {        while (!linkFirst(node))            notFull.await();    } finally {        lock.unlock();    }}

putFirstInserting a NULL element is not supported, a new object is created first Node , then the calling ReentrantLock lock method acquires the lock, the insert operation is boolean linkFirst(Node<E> node) implemented, and if the current queue header is full, the thread waits (the linkFirst method releases the lock signal after the element is written successfully), and finally, Release the lock in the finally block ( ReentrantLock use).

public E takeFirst() throws InterruptedException {    final ReentrantLock lock = this.lock;    lock.lock();    try {        E x;        while ( (x = unlinkFirst()) == null)            notEmpty.await();        return x;    } finally {        lock.unlock();    }}

putFirstSimilarly, takeFirst the lock is first acquired, then a reference to the tail element object is lifted in the try, and if unlinkFirst it is empty, the queue is empty and no element can be deleted, then the thread waits. Again, the lock is finally released in the finally block.

So the question is, LinkedBlockingDeque why not use LinkedBlockingQueue the read-write lock separation method? LinkedBlockingDeque LinkedBlockingQueue What is the difference between a usage scenario?

1.3 Delayqueue

DelayQueueIt is mainly used to implement delay tasks, such as: waiting for a period of time after closing the connection, cache object expiration Delete, task timeout processing, etc., the common feature of these tasks is to wait for a period of time after execution (similar to timertask). DelayQueueimplementation consists of three core features:

    • Deferred task: DelayQueue the generic class needs to inherit from the Delayed interface, and the Delayed interface inherits from Comparable<Delayed> , for the comparison of precedence in the queue;
    • Priority queue: DelayQueue the implementation of the priority queue PriorityQueue , that is, the shorter the delay time of the task the more first (recall the priority queue in the implementation of the binary heap).
    • Blocking queue: Supports concurrent read and write, ReentrantLock the use of the lock operation to achieve read and write.

Therefore, DelayQueue = Delayed + PriorityQueue + BlockingQueue .

public interface Delayed extends Comparable<Delayed> {    long getDelay(TimeUnit unit);}
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {    private final transient ReentrantLock lock = new ReentrantLock();    private final PriorityQueue<E> q = new PriorityQueue<E>();    private Thread leader = null;    private final Condition available = lock.newCondition();}

DelayQueueHow does the read and write operation implement the delay task next?

public boolean offer(E e) {    final ReentrantLock lock = this.lock;    lock.lock();    try {        q.offer(e);        if (q.peek() == e) {            leader = null;            available.signal();        }        return true;    } finally {        lock.unlock();    }}

The lock operation is performed first, then element e is inserted into the priority queue, and the priority queue calls the generic E's CompareTo method For comparison (for the operation of the binary heap, which is not described here, refer to the Data structure section of the correlation analysis), Adds a task with the shortest delay time to the team header. Finally check whether the next element is a team header, if it is a team header, set leader to empty, wake up all waiting queues, release the lock.

Public E take () throws Interruptedexception {final Reentrantlock lock = This.lock;    Lock.lockinterruptibly (); try {for (;;)            {E first = Q.peek ();            if (first = = null) available.await ();                else {Long delay = First.getdelay (nanoseconds);                if (delay <= 0) return Q.poll (); first = null;                Don ' t retain ref while waiting if (leader! = NULL) available.await ();                    else {Thread thisthread = Thread.CurrentThread ();                    leader = Thisthread;                    try {Available.awaitnanos (delay);                    } finally {if (leader = = Thisthread) leader = null;            }}}}} finally {if (leader = = null && q.peek () = null) Available.signal();    Lock.unlock (); }}
    • The lock operation is performed first, then the queue header is removed, and if the head is empty, the thread is blocked;
    • Gets the delay time of the head element, if the delay time is less than or equal to 0, indicating that the element has reached the time that can be used, call the poll method to eject the element;
    • When the delay time is greater than 0 o'clock, the first reference is released (avoiding memory leaks), and second, if the leader thread is not empty, then the thread is blocked (indicating that threads are waiting). Otherwise, assign the current thread to the leader element and then block the delay time, which is to wait for the queue header to arrive at the delay time, releasing the reference to the leader element in the finally block. After the loop, remove the enemy element and exit the For loop.
    • Finally, if leader is empty and the priority queue is not empty (judging if there are any other subsequent nodes), call signal notifies the other thread and performs the unlock operation.
1.4 Transferqueue and LinkedTransferQueue

TransferQueueis an inherited BlockingQueue interface and adds a number of new methods. LinkedTransferQueueis an TransferQueue implementation class for an interface that is defined as an unbounded queue, with an FIFO-first-out feature.

TransferQueueInterfaces mainly include the following methods:

public interface TransferQueue<E> extends BlockingQueue<E> {    boolean tryTransfer(E e);    void transfer(E e) throws InterruptedException;    boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;    boolean hasWaitingConsumer();    int getWaitingConsumerCount();}
    • Transfer (E): If a consumer thread is currently waiting to be acquired, it is immediately handed over; otherwise, the current element E is inserted into the tail of the queue and waits for the blocking state to take the element away from the consumer thread.
    • Trytransfer (E): If there is currently a consumer thread waiting to be acquired (using the take () or poll () function), using this method will immediately transfer/Transfer the object element E, and if it does not, return false and do not enter the queue. This is a non-blocking operation.
    • Trytransfer (e E, long timeout, timeunit unit): If there is currently a consumer thread waiting to be acquired, it will be transferred to it immediately, otherwise the element E will be inserted at the end of the queue and wait for the consumer thread to consume If the element e cannot be fetched by the consumer thread within the specified time, false is returned and the element is removed.
    • Haswaitingconsumer (): Determines whether there is a consumer thread.
    • Getwaitingconsumercount (): Gets the number of consumer threads waiting to get the element.

LinkedTransferQueueImplementation of the above method, compared to LinkedBlockingQueue when the queue is full, the queued operation will be blocked LinkedTransferQueue , when the queue is dissatisfied can also block, as long as there is no consumer use elements. Here LinkedTransferQueue 's a look at the enqueue and the out-of-team operations: transfer and take methods.

public void transfer(E e) throws InterruptedException {    if (xfer(e, true, SYNC, 0) != null) {        Thread.interrupted(); // failure possible only due to interrupt        throw new InterruptedException();    }}
public E take() throws InterruptedException {    E e = xfer(null, false, SYNC, 0);    if (e != null)        return e;    Thread.interrupted();    throw new InterruptedException();}

LinkedTransferQueueA key approach is used by both the team and the teams:

private E xfer(E e, boolean haveData, int how, long nanos) {}

Wherein, E represents the element to be manipulated, to represent the addition of haveData true data, to false remove the data, how there are four kinds of values: NOW ,, ASYNC SYNC or, respectively, the TIMED timing of execution, nanos expressed how as TIMEDtime limit.
( xfer the method is more complex, and it is no longer unfolding.) In addition, the LinkedTransferQueue CAs non-blocking synchronization mechanism is used, which is described later.

Java Concurrency (i)--thread-safe containers (top)

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.