Blocking queue
Blocking queues are often used in producer consumer scenarios. In java8, JUC provides seven blocking queues.
Class name | effect |
---|---|
ArrayBlockingQueue | The bounded blocking queue implemented by the array sorts the elements according to the first in first out (FIFO) principle. |
LinkedBlockingQueue | A bounded blocking queue implemented by a linked list. The default and maximum length of this queue is integer MAX_ VALUE. This queue sorts elements on a first in, first out basis |
PriorityBlockingQueue | Unbounded blocking queue that supports prioritization. By default, elements are arranged in natural ascending order. You can also customize the class to implement the compareTo() method to specify the element arrangement rules, or specify the construction parameter Comparator to sort the elements when initializing PriorityBlockingQueue. |
DelayQueue | Unbounded blocking queue implemented by priority queue |
SynchronousQueue | Do not store the blocking queue of elements. Each put operation must wait for a take operation, otherwise you cannot continue to add elements. |
LinkedTransferQueue | Unbounded blocking queue implemented by linked list |
LinkedBlockingDeque | Bidirectional blocking queue implemented by linked list |
Operation method of blocking queue
Insert operation
add(e): add elements to the queue. If the queue is full, an error will be reported if you continue to insert elements, such as IllegalStateException.
offer(e): when adding an element to the queue, it will return the status of whether the element was successfully inserted. If it is successful, it will return true
put(e): when the blocking queue is full, the producer continues to add elements through put, and the queue will block the producer thread until the queue is available
offer(e,time,unit): if you continue to add elements after the blocking queue is full, the producer thread will be blocked for a specified time. If it times out, the thread will exit directly
Remove operation
remove(): when the queue is empty, calling remove will return false. If the element is removed successfully, it will return true
poll(): when there are elements in the queue, an element will be taken from the queue. If the queue is empty, null will be returned directly
take(): get the elements in the queue based on blocking. If the queue is empty, the take method will block until there is new data in the queue that can be consumed
poll(time,unit): get data with timeout mechanism. If the queue is empty, it will wait for the specified time to get the element return
Principle analysis of ArrayBlockingQueue
Construction method
ArrayBlockingQueue has three constructors.
capacity: indicates the length of the array, that is, the length of the queue.
Fair: indicates whether it is a fair blocking queue. By default, an unfair blocking queue is constructed
c: Indicates the initialization of the given data.
public ArrayBlockingQueue(int capacity) { this(capacity, false); } 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(); } public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { this(capacity, fair); final ReentrantLock lock = this.lock; lock.lock(); // Lock only for visibility, not mutual exclusion try { int i = 0; try { for (E e : c) { checkNotNull(e); items[i++] = e; } } catch (ArrayIndexOutOfBoundsException ex) { throw new IllegalArgumentException(); } count = i; putIndex = (i == capacity) ? 0 : i; } finally { lock.unlock(); } }
add method
public boolean add(E e) { //Call the add method of the parent class, that is, abstractqueue return super.add(e); } //Method of abstractqueue class public boolean add(E e) { //Call the off method, and finally call back the offer method in ArrayBlockingQueue if (offer(e)) return true; else throw new IllegalStateException("Queue full"); } public boolean offer(E e) { checkNotNull(e); //Judge whether the added data is empty final ReentrantLock lock = this.lock; lock.lock(); //Lock try { if (count == items.length) // Judge the queue length. If the queue length is equal to the array length, it indicates that it is full and returns false directly return false; else { enqueue(e); //enqueue is called to add an element to the queue return true; } } finally { lock.unlock(); } } private void enqueue(E x) { final Object[] items = this.items; items[putIndex] = x; // When putIndex is equal to the length of the array, reset putIndex to 0 //Because it is FIFO, when the elements in the queue are full, you need to start from scratch if (++putIndex == items.length) putIndex = 0; count++; //Record the number of queue elements notEmpty.signal();//Wake up the thread in the waiting state, indicating that the element in the current queue is not empty. If there is a consumer thread blocking, you can start to take out the element }
put method
The function of the put method is the same as that of the add method. The difference is that the put method will block if the queue is full.
public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; //This is also to obtain a lock, but the difference from lock is that this method preferentially allows other threads to call the interrupt method of the waiting thread to interrupt the wait //return. The lock method is to respond to the interrupt only after the attempt to obtain the lock is successful lock.lockInterruptibly(); try { while (count == items.length) //When the queue is full, the current thread will be suspended by the notFull condition object and added to the waiting queue notFull.await(); enqueue(e); } finally { lock.unlock(); } }
take method
The take method is a method of blocking the acquisition of elements in the queue. Its implementation principle is very simple. If there is a block, delete it or not. This blocking can be interrupted. If there is no data in the queue, join the notEmpty condition queue to wait (if there is data, take it directly and the method ends). If a new put thread adds data, the put operation will wake up the take thread and execute the take operation
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) //If the queue is empty, it will be blocked directly through await method. If there is a thread add or put, it will be blocked through notempty Signal () thread notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; //Set the element at this location to null //If you get the last element of the array, reset it to 0 and continue to get data from the head position if (++takeIndex == items.length) takeIndex = 0; count--; //Decreasing number of queue elements if (itrs != null) itrs.elementDequeued(); //If the iterator is not empty, the data in the iterator is updated //Wake up a write thread that is blocked because the queue is full notFull.signal(); return x; }
remove method
The remove method removes a specified element.
public boolean remove(Object o) { if (o == null) return false; final Object[] items = this.items; final ReentrantLock lock = this.lock; lock.lock(); try { if (count > 0) { final int putIndex = this.putIndex; //Gets the index of the next element to be added int i = takeIndex; do { if (o.equals(items[i])) { //Starting with the takeIndex subscript, find the element to be deleted removeAt(i); //Removes the specified element return true; } if (++i == items.length) i = 0; } while (i != putIndex); } return false; } finally { lock.unlock(); } }
Principle diagram of ArrayBlockingQueue
When the thread executes the add or put method, it will put the current element into items[putIndex], and then putindex + 1 and count + 1. When the thread executes the take method, it will get items[takeIndex], and then take index + 1, count-1
Atomic operation class
The so-called atomicity means that one or more operations are either completed or none are executed. There can be no success and failure. For example, i=0; Executing i + + is divided into three steps. 1. Get the value of i, 2, i+1, 3, assign the result to i. When 10 threads execute i + + in parallel, the result may be less than 10. This is because when executing step 1, multiple threads may get the i value of 1 at the same time. This is a typical atomicity problem. It can be solved by locking it. And from jdk1 Starting from 5, JUC provides Atomic package, which provides Atomic operations for common data structures. It provides a simple, efficient, and thread safe way to update a variable.
Due to the relationship between variable types, 12 classes of atomic operations are provided in JUC. These 12 categories can be divided into four categories
1. Atomic update basic type
AtomicBoolean,AtomicInteger,AtomicLong
2. Update atom array
AtomicIntegerArray , AtomicLongArray ,AtomicReferenceArray
3. Atomic update reference
AtomicReference, AtomicReferenceFieldUpdater, AtomicMarkableReference (update reference type with marker bit)
5. Atomic update field
AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicStampedReference