The blocking queue of java juc concurrent packets-BlockQueue

After learning Java's synchronization queue, Lock, and waiting notification mechanism, looking at the blocking queue will make it easier to understand the blocking queue. A blocking queue is a queue that supports two additional operations. These two additional operations support blocking insertion: when the queue is full, the queue will block the thread that inserts the element until the queue is not full; blocking removal: when the queue is empty, the thread that gets the element will wait for the queue to become non-empty. When the blocking queue is not available, these two additional operations provide 4 processing methods

Method/processing method

Throw an exception

Return special value

Keeps blocking

Timeout exit

Insert method

add(e)

offer(e)

put(e)

offer(e, time, unit)

Removal method

remove()

poll()

take()

poll(time,unit)

Inspection Method

element()

peek()

-

-

Throwing an exception means that when the queue is full, if you insert an element into the queue, an IllegalStateException ("Queue full") exception will be thrown. When the queue is empty, getting elements from the queue will throw NoSuchElementException.

Returning a special value means that when inserting an element into the queue, it will return whether the element is inserted successfully, and return true if it succeeds. If it is a removal method, it will take an element from the queue, and return null if it is not.

·Always blocking means that when the blocking queue is full, if the producer thread puts elements into the queue, the queue will keep blocking the producer thread until the queue is available or exits in response to an interrupt. When the queue is empty, if the consumer thread takes elements from the queue, the queue will block the consumer thread until the queue is not empty. We will introduce the source code of this mode later.
·Timeout exit means that when the blocking queue is full, if the producer thread inserts elements into the queue, the queue will block the producer thread for a period of time. If the specified time is exceeded, the producer thread will exit.

JDK provides us with several blocking queues. Their implementation methods are almost the same. We will describe the implementation of blocking queues in a way later. The following table shows the several blocking queues and descriptions provided by JDK:

Blocking queue description
ArrayBlockingQueue A bounded blocking queue composed of an array structure
LinkedBlockingQueue A bounded blocking queue composed of a linked list structure
PriorityBlockingQueue An unbounded blocking queue that supports priority sorting
DelayQueue An unbounded blocking queue implemented using priority queues
SynchronousQueue A blocking queue that does not store elements
LinkedTransferQueue An unbounded blocking queue composed of a linked list structure
LinkedBlockingDeque A two-way blocking queue composed of a linked list structure

As above, for the blocking queues provided by jdk for us, if we look at their source code, we find that their variable definitions have Lock interface and Condition interface as fields, that is, their implementation is based on Lock and Condition. As long as we master the use of Lock and Condition, it is easy to master the use of blocking queues. Let's take LinkedBlockingQueue as an example, and the definition code is as follows:

/** Lock held by take, poll, etc */ take、poll操作时获取锁
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */等待take操作
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */ put offer时获取锁
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */ //等待put操作
private final Condition notFull = putLock.newCondition();

Next, we have to see how LinkedBlockingQueue stores and retrieves elements. First, we take the storage element as an example, and we take the put method as an example. The code is as follows:

//唤醒一个take等操作的线程
private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
}
 public void put(E e) throws InterruptedException {
        //如果传值为null,抛出异常
        if (e == null) throw new NullPointerException();
        int c = -1;
        //创建节点
        Node<E> node = new Node<E>(e);
        //获取putLock实例
        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();
        }
        //如果为0
        if (c == 0)
            //唤醒一个取出元素的线程
            signalNotEmpty();
    }

We have introduced the source code of storing elements above. The logic of storing other elements is similar to the above, so we won’t introduce them here. Let’s analyze the source code of obtaining elements: take method. The source code is as follows:

//唤醒一个put操作的线程
 private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
} 
public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //获取take锁
        takeLock.lockInterruptibly();
        try {
            //如果队列元素为空,获取线程等待
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            //如果队列元素不为空,唤醒取元素线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            //释放锁
            takeLock.unlock();
        }
        //如果c=capacity 唤醒put操作的线程
        if (c == capacity)
            signalNotFull();
        return x;
    }

Above we introduced the put and take operations of LinkedBlockingQueue. We can know that the main principle of the blocking queue is to wait for the notification mechanism, using the Lock, Condition, and LockSupport components provided by the concurrent package. As long as we are familiar with the use of these three components, we can easily implement our own blocking queue. If you want to know the specific implementation of other blocking queues, you can refer to the source code yourself. The core is the waiting notification mechanism and the three components mentioned above.

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108456825