Deep understanding of blocking queue Container _ Basics in Java threading programming

Source: Internet
Author: User
Tags diff sorts

1. What is a blocking queue?

A blocking queue (Blockingqueue) is a queue that supports two additional operations. The two additional actions are that when the queue is empty, the thread that gets the element waits for the queue to become non-null. When the queue is full, the thread that stores the elements waits for the queue to be available. Blocking queues are often used by producers and consumers, and producers are threads that add elements to the queue, and consumers are the threads that take elements from the queue. A blocking queue is a container in which the producer stores elements, and the consumer takes only the elements from the container.

Blocking queues provide four ways to handle this:

Throw an exception: refers to the illegalstateexception ("Queue full") exception thrown when the blocked queue is filled and the element is inserted into the queue. When the queue is empty, the nosuchelementexception exception is thrown when the element is fetched from the queue.
Returns a special value: The Insertion method returns success and returns True. To remove a method, you take an element out of the queue, and if not, return null
Blocking: When the blocked queue is full, if the producer threads put elements into the queue, the queue blocks the producer thread until it gets the data, or the response breaks out. When the queue is empty, the consumer thread attempts to take elements from the queue, and the queue blocks the consumer thread until the queue is available.
Timeout exit: When the blocked queue is full, the queue blocks the producer thread for a period of time, and if more than a certain amount of time, the producer thread exits.
2. Blocking queues in Java

The JDK7 provides 7 blocking queues. respectively is

    1. Arrayblockingqueue: A bounded blocking queue consisting of an array structure.
    2. Linkedblockingqueue: A bounded blocking queue consisting of a list structure.
    3. Priorityblockingqueue: An unbounded blocking queue that supports priority sorting.
    4. Delayqueue: An unbounded blocking queue implemented using a priority queue.
    5. Synchronousqueue: A blocking queue that does not store elements.
    6. LinkedTransferQueue: An unbounded blocking queue consisting of a list structure.
    7. Linkedblockingdeque: A two-way blocking queue consisting of a linked list structure.

Arrayblockingqueue is a bounded blocking queue implemented with arrays. This queue sorts the elements according to the first-in first out (FIFO) principle. By default, visitors are not guaranteed a fair access queue, the so-called fair Access queue is the blocking of all producer threads or consumer threads, when the queue is available, you can access the queue in a blocking sequence, that is, the first blocked producer thread, you can first insert elements into the queue, first blocked consumer threads, You can get the elements from the queue first. Typically, the throughput is reduced to ensure fairness. We can create a fair blocking queue using the following code:

Arrayblockingqueue fairqueue = new Arrayblockingqueue (1000,true);

The fairness of the visitor is implemented using a reentrant lock, as follows:

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 ();
}

Linkedblockingqueue is a bounded blocking queue implemented with a linked list. The default and maximum length for this queue is integer.max_value. This queue sorts the elements according to the FIFO principle.

Priorityblockingqueue is an unbounded queue that supports precedence. By default, the elements are sorted in a natural order, or you can specify the collation of the elements through the comparer comparator. The elements are sorted in ascending order.

Delayqueue is an unbounded blocking queue that supports delay-fetching elements. Queues are implemented using Priorityqueue. The elements in the queue must implement the delayed interface, and you can specify how long to get the current element from the queue when the element is created. The element can be extracted from the queue only when the delay expires. We can apply delayqueue to the following scenarios:

Caching system Design: You can use Delayqueue to save the expiration of the cache element, use a thread loop query Delayqueue, when the element can be obtained from delayqueue, the cache expiration date.
Scheduled task scheduling. Use Delayqueue to save the task and execution time that will be performed on that day, once you get the task from the Delayqueue to start execution, from example Timerqueue is the use of delayqueue implementation.
The delayed in the queue must implement CompareTo to specify the order of the elements. For example, let the longest delay time at the end of the queue. The implementation code is as follows:

public int CompareTo (delayed other) {
      if (other = =)//compare zero only if same object return
        0;
      if (other instanceof scheduledfuturetask) {
        scheduledfuturetask x = (scheduledfuturetask) Other;
        Long diff = time-x.time;
        if (diff < 0)
          return-1;
        else if (diff > 0) return
          1;
  else if (SequenceNumber < x.sequencenumber)
          return-1;
        else return
          1;
      }
      Long d = (Getdelay (timeunit.nanoseconds)-
           Other.getdelay (timeunit.nanoseconds));
      return (d = = 0)? 0: ((d < 0)? -1:1);
    }

3. How to implement delayed interface

We can refer to the Scheduledfuturetask class in Scheduledthreadpoolexecutor. This class implements the delayed interface. First: When an object is created, when the object can be used with the time record, the code is as follows:


Scheduledfuturetask (Runnable R, V result, long NS, long period) {
      super (R, result);
      This.time = ns;
      This.period = period;
      This.sequencenumber = Sequencer.getandincrement ();
}

You can then use Getdelay to query the current element for how long it will take, and the code is as follows:

Public long Getdelay (timeunit unit) {return
      Unit.convert (Time-now (), timeunit.nanoseconds);
    

Through the constructor can see the delay time parameter NS unit is nanosecond, it is best to use nanosecond when you design, because Getdelay can specify any unit, once in nanoseconds as a unit, and delay time and precision less than nanosecond on the trouble. When used, note that when time is less than current, Getdelay returns a negative number.

4. How to realize the delay queue

The implementation of the delay queue is very simple, when the consumer gets the element from the queue, if the element does not reach the delay time, the current thread is blocked.

Long delay = First.getdelay (timeunit.nanoseconds);
          if (delay <= 0) return
            q.poll ();
          else if (leader!= null)
            available.await ();

Synchronousqueue is a blocking queue that does not store elements. Each put operation must wait for a take operation, or you cannot continue adding elements. Synchronousqueue can be seen as a passer, responsible for the direct transmission of data from producer threads to consumer threads. The queue itself does not store any elements, and is ideal for transitive scenarios, such as data used in one thread, passed to another thread, with synchronousqueue throughput higher than Linkedblockingqueue and Arrayblockingqueue.

LinkedTransferQueue is an unbounded blocking Transferqueue queue composed of a list structure. Compared to other blocking queues, LinkedTransferQueue has more trytransfer and transfer methods.

Transfer method. If a consumer is currently waiting for the receiving element (the consumer uses the Take () method or the poll () method with a time limit), the transfer method can immediately transfer (transmit) to the consumer the elements passed in by the producer. If no consumer waits for the receiving element, the transfer method stores the element in the tail node of the queue and waits until the element is consumed by the consumer before returning. The key code for the transfer method is as follows:

Node pred = Tryappend (s, havedata);
Return to Awaitmatch (S, Pred, E, (how = = TIMED), Nanos);

The first line of code is an attempt to hold the S node of the current element as the tail node. The second line of code is to let the CPU spin for consumer consumption elements. Because the spin consumes the CPU, the Thread.yield () method is used after a certain number of spins to suspend the currently executing thread and execute other threads.

Trytransfer method. is used to test whether the incoming elements of the producer can be passed directly to the consumer. Returns False if there is no consumer waiting to receive the element. The difference between the transfer method and the Trytransfer method is that the method returns immediately, regardless of whether the consumer receives it. The transfer approach is to wait until the consumer is consumed before returning.

For the Trytransfer (e E, long timeout, Timeunit unit) method with time limits, it is an attempt to pass the element passed on by the producer directly to the consumer, but if no consumer consumes the element, waits for the specified time to return, if the timeout is not consumed, Returns False if the element is consumed within the time-out period, returns true.

Linkedblockingdeque is a two-way blocking queue consisting of a list structure. The so-called two-way queue means that you can insert and remove elements from both ends of the queue. Two-terminal queue because of a more than one operation queue entrance, in the simultaneous entry of multithreading, also reduced by half the competition. Compared to other blocking queues, Linkedblockingdeque is more addfirst,addlast,offerfirst,offerlast,peekfirst,peeklast and so on, with the method ending with the word, the insertion, Gets (peek) or removes the first element of a two-terminal queue. The method that ends the last word, which represents inserting, getting, or removing the final element of a two-terminal queue. In addition, insert method add is equivalent to AddLast, and removing method remove is equivalent to Removefirst. But the take approach is equivalent to Takefirst, not knowing if it is a JDK bug, or using a method with the one and last suffixes to make it clearer.

Capacity can be set to prevent the transition from expanding when Linkedblockingdeque is initialized. In addition, bidirectional blocking queues can be used in "work-stealing" mode.

5. The implementation principle of the blocking queue
This article takes Arrayblockingqueue as an example, other blocking queue implementation principle may and arrayblockingqueue some differences, but the general thinking should be similar, interested friends can see the other blocking queue to realize the source code.

First look at several member variables in the Arrayblockingqueue class:

public class Arrayblockingqueue<e> extends abstractqueue<e>
implements Blockingqueue<e>, java.io.Serializable {
 
private static final long serialversionuid = -817911632652898426l;
 
/** 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;
}

As you can see, the arrayblockingqueue for storing the elements is actually an array, Takeindex and Putindex represent the subscript of the team's first element and the tail element, and count represents the number of elements in the queue.

Lock is a reentrant lock, Notempty and notfull are waiting conditions.

Here's a look at the Arrayblockingqueue constructor, which has three overloaded versions:

public arrayblockingqueue (int capacity) {
} public
arrayblockingqueue (int capacity, Boolean fair) {
 
}
public arrayblockingqueue (int capacity, Boolean fair,
             collection<? extends e> c) {
}

The first constructor has only one parameter to specify capacity, the second constructor can specify capacity and fairness, and the third constructor can specify capacity, fairness, and initialization with another set.

Then look at the implementation of its two key methods: put () and take ():

public void put (e e) throws interruptedexception {
  if (E = = null) throw new NullPointerException ();
  Final e[] items = this.items;
  Final Reentrantlock lock = This.lock;
  Lock.lockinterruptibly ();
  try {
    try {while
      (count = = items.length)
        notfull.await ();
    \ catch (Interruptedexception IE) {
      notfull.signal ();//Propagate to non-interrupted thread
      throw ie;
    Insert (e);
  } finally {
    lock.unlock ();
  }
}

As can be seen from the implementation of the Put method, it acquires the lock first and gets the interruptible lock, then determines whether the current element is equal to the length of the array, and if it is equal, calls notfull.await () to wait, and if the interrupt exception is caught, wake the thread and throw an exception.

When awakened by another thread, inserts an element with the Insert (e) method and unlocks the last.

Let's look at the implementation of the Insert method:

private void Insert (E x) {
  Items[putindex] = x;
  Putindex = Inc (PUTINDEX);
  ++count;
  Notempty.signal ();
}

It is a private method that wakes up the thread that is waiting to fetch the element through notempty after successful insertion.

The following is the implementation of the Take () method:

Public E take () throws Interruptedexception {
  final reentrantlock lock = This.lock;
  Lock.lockinterruptibly ();
  try {
    try {while
      (count = = 0)
        notempty.await ();
    } catch (Interruptedexception IE) {
      Notempty.signal (); Propagate to non-interrupted thread
      throw ie;
    E x = extract ();
    return x;
  } finally {
    lock.unlock ();
  }
}


Similar to the Put method implementation, but the put method waits for the notfull signal, and the take method waits for the notempty signal. In the Take method, if an element can be taken, the element is obtained by the Extract method, and the following is the implementation of the Extract method:


Private E Extract () {
  final e[] items = this.items;
  E x = Items[takeindex];
  Items[takeindex] = null;
  Takeindex = Inc (TAKEINDEX);
  --count;
  Notfull.signal ();
  return x;
}

It's similar to the Insert method.

In fact, from here we should understand the principle of the blocking queue implementation, and the fact that it is similar to the way we implement producer-consumer Thinking with object.wait (), object.notify () and non-blocking queues, except that it integrates the work into the blocking queue.

Related Article

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.