Java multi-threaded -BlockingQueue

  

  • Inheritance structure of BlockingQueue

  BlockingQueue is thread-safe blocking queue when the queue is empty, pull the thread queue waiting queue again there are elements; when the queue is full, add an element of thread will wait queue has space to store the new element. BlockingQueue inheritance interfaces are as follows:

 

  • Producer - consumer model

  ArrayBlokingQueue implementation class to set a fixed size, only one SynchronousQueue capacity, variable capacity LinkedBlockingQueue queue. Generally, BlockingQueue producer-consumer scenario applies, i.e., producer - consumer, by definition, the producer is added to the queue in the thread element, the consumer is the thread element from the queue, the code template generally written as follows:

/**
 * 生产者
 */  
class Producer implements Runnable {
    private final BlockingQueue queue;
    Producer(BlockingQueue q) { queue = q; }
    public void run() {
      try {
        while (true) { queue.put(produce()); }
      } catch (InterruptedException ex) { ... handle ...}
    }
    Object produce() { ... }
 }
 
/*
 * 消费者
 */
 class Consumer implements Runnable {
    private final BlockingQueue queue;
    Consumer(BlockingQueue q) { queue = q; }
    public void run() {
      try {
        while (true) { consume(queue.take()); }
      } catch (InterruptedException ex) { ... handle ...}
    }
    void consume(Object x) { ... }
 }
 
  class Setup {
    void main() {
      BlockingQueue q = new SomeQueueImplementation();
      Producer p = new Producer(q);
      Consumer c1 = new Consumer(q);
      Consumer c2 = new Consumer(q);
      new Thread(p).start();
      new Thread(c1).start();
      new Thread(c2).start();
    }

 

It should be noted that both producers and consumers must act on the same blocking queue.



  • Data structure for storing data BlockingQueue

  LinkedBlockingQueue internal storage structure is a linked list, we define an inner class,

 1 /**
 2      * Linked list node class
 3      */
 4     static class Node<E> {
 5         E item;
 6 
 7         /**
 8          * One of:
 9          * - the real successor Node
10          * - this Node, meaning the successor is head.next
11          * - null, meaning there is no successor (this is the last node)
12          */
13         Node<E> next;
14 
15         Node(E x) { item = x; }
16     }

As can be seen this is a singly linked list, since each node only stores Node value of the current node, Node and a reference point to the next node. Similarly, according to ArrayBlockingQueue with the name, can be inferred, the internal storage array structure is not repeated here.

  When constructing a LinkedBlockingQueue, the default constructor, creates a maximum number of nodes of Integer.MAX_VALUE queue and creates a node Node object whose value is null, last reference point to the head and this would achieve

 1 /**
 2    * Creates a {@code LinkedBlockingQueue} with a capacity of
 3    * {@link Integer#MAX_VALUE}.
 4    */
 5 public LinkedBlockingQueue() {
 6      this(Integer.MAX_VALUE);
 7 }
 8 
 9 
10 public LinkedBlockingQueue(int capacity) {
11      if (capacity <= 0) throw new IllegalArgumentException();
12      this.capacity = capacity;
13      last = head = new Node<E>(null);
14 }

Queue initialization. Then you can put up and take.

 

  • Achieve thread safety when put and take

   First look at the put () method, namely the element into the queue, how to achieve thread-safe.

 public  void PUT (E E) throws InterruptedException {
         IF (E == null ) the throw  new new a NullPointerException ();
         int C = -1 ;
         // package a new node element is 
        the Node <E> = Node new new the Node <E> ( E);
         // use put reentrant lock object 
        Final of ReentrantLock = putLock the this .putLock;
         Final of AtomicInteger COUNT = the this .count;
         // can be interrupted blocking 
        putLock.lockInterruptibly ();
         the try {
            // Point1 : If the current number of elements to achieve the maximum capacity of the queue, the lock is released and suspends the current thread, until notFull.signal () wake 
            the while (count.get () == Capacity) { 
                notFull.await (); 
            } 
            / / node enqueue 
            the enqueue (node);
             // number of enqueued before the +1 queue is less than the maximum capacity of the container, is called notFull.signal () wait wakeup code above thread 
            C = count.getAndIncrement ();
             IF (C . 1 + < Capacity) 
                notFull.signal (); 
        } the finally { 
            putLock.unlock ();    // release lock 
        }
         // this code is to wake up take (the pending thread), the following Detailed specific reasons 
        if (c == 0)
            signalNotEmpty();
    }

put way to ensure thread safety lock mechanism is based on re-entry, easier to understand, suppose thread A executed to Point1 , if the current maximum capacity of the list, then enter while in suspend a thread, otherwise continue.

Suppose there is such a scene, A thread execution put, at this time the queue is full, then thread A will be suspended at point1, then who is going to wake up the thread A? The answer is to take () method, see take the following method

. 1  public E Take () throws InterruptedException {
 2          E X;
 . 3          int C = -1 ;
 . 4          Final of AtomicInteger COUNT = the this .count;
 . 5          Final of ReentrantLock = takeLock the this .takeLock;
 . 6          takeLock.lockInterruptibly ();
 . 7          the try {
 . 8              / / piont1 : If the current queue is empty, and the hung thread releases the lock 
. 9              the while (count.get () == 0 ) {
 10                  notEmpty.await ();
 . 11              }
 12 is              //Dequeue end element 
13 is              X = dequeue ();
 14              // if the queue capacity is not empty, the wake-up threads waiting in the take 
15              C = count.getAndDecrement ();
 16              IF (C>. 1 )
 . 17                  notEmpty.signal ( );
 18 is          } the finally {
 . 19              takeLock.unlock ();
 20 is          }
 21 is          // Point2 : C is the capacity before the take, i.e., the current capacity of c-1, put awaken the waiting thread 
22 is          IF (C == capacity)
 23 is              signalNotFull ();
 24          return X;
 25      }
26 
27 
28     private void signalNotFull() {
29         final ReentrantLock putLock = this.putLock;
30         putLock.lock();
31         try {
32             notFull.signal();
33         } finally {
34             putLock.unlock();
35         }
36     }

Suppose the front of the current queue is full, then put in a blocked thread A has put () method Point1 position until the thread B executed take () method, an element is removed, and then perform signalNotFull method, put the thread wake. The signalNotEmpty put () method () method is just the opposite, that the container is 0, there is a thread to perform the take () blocks until put to wake take thread.

 

  It can be seen from the above, cooperation between BlockingQueue use reentrant lock to ensure thread safety, use Condition objects await () and sigal () coordinating thread to achieve the effect of thread-safe blocking queue, AtomicInteger the object count is put and take an important bridge between, which represents the number of elements currently in the queue to ensure atomicity get increased number of elements, it is not impossible to ensure the correct data. There are many implementation details, the code are taken into account, such as when the container is empty, the container is full, the container that is not empty is not full, then signalNotFull () and signalNotEmpty are they will not be executed a.

Guess you like

Origin www.cnblogs.com/yxlaisj/p/12215324.html