Java Blocking Queue Implementation principle analysis-arrayblockingqueue and Linkedblockingqueue

Source: Internet
Author: User

The blocking queue interface in Java Blockingqueue inherits from the queue interface.

The Blockingqueue interface provides 3 methods of adding elements.

    1. Add: Adding elements to the queue, adding success returns True, IllegalStateException exception thrown because of a full capacity add failure
    2. Offer: add element to queue, add success return true, add failure return False
    3. Put: Add elements to the queue, if the capacity is full will block until the capacity is dissatisfied

3 Methods of deletion.

    1. Poll: Deletes the queue header element and returns null if the queue is empty. Otherwise, the element is returned.
    2. Remove: Locate the corresponding element based on the object and delete it. Delete succeeds returns True, otherwise returns false
    3. Take: Removes the queue header element, if the queue is empty, blocks until the queue has elements and deletes

Commonly used blocking queue specific classes have Arrayblockingqueue, Linkedblockingqueue, Priorityblockingqueue, Linkedblockingdeque and so on.

Taking Arrayblockingqueue and Linkedblockingqueue as examples, this paper analyzes their realization principle.

Arrayblockingqueue

The principle of arrayblockingqueue is to use a reentrant lock and two conditional objects generated by this lock for concurrency control (classic two-condition algorithm).

Arrayblockingqueue is a blocking queue with a length, which must be initialized to specify the queue length, and no modifications are allowed after the specified length.

It comes with the following properties:

// 存储队列元素的数组,是个循环数组final Object[] items;// 拿数据的索引,用于take,poll,peek,remove方法int takeIndex;// 放数据的索引,用于put,offer,add方法int putIndex;// 元素个数int count;// 可重入锁final ReentrantLock lock;// notEmpty条件对象,由lock创建private final Condition notEmpty;// notFull条件对象,由lock创建private final Condition notFull;
The addition of data

Arrayblockingqueue has a number of different methods for adding data, add, offer, put method.

Add Method:

public boolean add(E e) {    if (offer(e))        return true;    else        throw new IllegalStateException("Queue full");}

The Add method internally calls the Offer method as follows:

public boolean offer(E e) {    checkNotNull(e); // 不允许元素为空    final ReentrantLock lock = this.lock;    lock.lock(); // 加锁,保证调用offer方法的时候只有1个线程    try {        if (count == items.length) // 如果队列已满            return false; // 直接返回false,添加失败        else {            insert(e); // 数组没满的话调用insert方法            return true; // 返回true,添加成功        }    } finally {        lock.unlock(); // 释放锁,让其他线程可以调用offer方法    }}

The Insert method is as follows:

private void insert(E x) {    items[putIndex] = x; // 元素添加到数组里    putIndex = inc(putIndex); // 放数据索引+1,当索引满了变成0    ++count; // 元素个数+1    notEmpty.signal(); // 使用条件对象notEmpty通知,比如使用take方法的时候队列里没有数据,被阻塞。这个时候队列insert了一条数据,需要调用signal进行通知}

Put method:

public void put(E e) throws InterruptedException {    checkNotNull(e); // 不允许元素为空    final ReentrantLock lock = this.lock;    lock.lockInterruptibly(); // 加锁,保证调用put方法的时候只有1个线程    try {        while (count == items.length) // 如果队列满了,阻塞当前线程,并加入到条件对象notFull的等待队列里            notFull.await(); // 线程阻塞并被挂起,同时释放锁        insert(e); // 调用insert方法    } finally {        lock.unlock(); // 释放锁,让其他线程可以调用put方法    }}

Arrayblockingqueue's Add data method has add,put,offer these 3 methods, summarized as follows:

The Add method internally calls the Offer method, and if the queue is full, throws a IllegalStateException exception, otherwise returns true

Offer method returns False if the queue is full, otherwise true

The Add method and the Offer method do not block threads, and the put method will block threads if the queue is full, until the thread consumes the data in the queue to be awakened.

The 3 methods internally use a reentrant lock to guarantee atomicity.

Deletion of data

Arrayblockingqueue has several different methods of data deletion, poll, take, and remove methods.

Poll Method:

public E poll() {    final ReentrantLock lock = this.lock;    lock.lock(); // 加锁,保证调用poll方法的时候只有1个线程    try {        return (count == 0) ? null : extract(); // 如果队列里没元素了,返回null,否则调用extract方法    } finally {        lock.unlock(); // 释放锁,让其他线程可以调用poll方法    }}

The poll method internally calls the Extract method:

private E extract() {    final Object[] items = this.items;    E x = this.<E>cast(items[takeIndex]); // 得到取索引位置上的元素    items[takeIndex] = null; // 对应取索引上的数据清空    takeIndex = inc(takeIndex); // 取数据索引+1,当索引满了变成0    --count; // 元素个数-1    notFull.signal(); // 使用条件对象notFull通知,比如使用put方法放数据的时候队列已满,被阻塞。这个时候消费了一条数据,队列没满了,就需要调用signal进行通知    return x; // 返回元素}

Take method:

public E take() throws InterruptedException {    final ReentrantLock lock = this.lock;    lock.lockInterruptibly(); // 加锁,保证调用take方法的时候只有1个线程    try {        while (count == 0) // 如果队列空,阻塞当前线程,并加入到条件对象notEmpty的等待队列里            notEmpty.await(); // 线程阻塞并被挂起,同时释放锁        return extract(); // 调用extract方法    } finally {        lock.unlock(); // 释放锁,让其他线程可以调用take方法    }}

Remove method:

public boolean remove(Object o) {    if (o == null) return false;    final Object[] items = this.items;    final ReentrantLock lock = this.lock;    lock.lock(); // 加锁,保证调用remove方法的时候只有1个线程    try {        for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) { // 遍历元素            if (o.equals(items[i])) { // 两个对象相等的话                removeAt(i); // 调用removeAt方法                return true; // 删除成功,返回true            }        }        return false; // 删除成功,返回false    } finally {        lock.unlock(); // 释放锁,让其他线程可以调用remove方法    }}

RemoveAt Method:

void removeAt(int i) {    final Object[] items = this.items;    if (i == takeIndex) { // 如果要删除数据的索引是取索引位置,直接删除取索引位置上的数据,然后取索引+1即可        items[takeIndex] = null;        takeIndex = inc(takeIndex);    } else { // 如果要删除数据的索引不是取索引位置,移动元素元素,更新取索引和放索引的值        for (;;) {            int nexti = inc(i);            if (nexti != putIndex) {                items[i] = items[nexti];                i = nexti;            } else {                items[i] = null;                putIndex = i;                break;            }        }    }    --count; // 元素个数-1    notFull.signal(); // 使用条件对象notFull通知,比如使用put方法放数据的时候队列已满,被阻塞。这个时候消费了一条数据,队列没满了,就需要调用signal进行通知 }

Arrayblockingqueue's Delete data method has poll,take,remove these 3 methods, summarized as follows:

The poll method returns null if the queue is empty, otherwise returns the queue header element.

The Remove method takes an element that is based on the subscript value of the object, and the delete succeeds returns true, otherwise false is returned.

The poll method and the Remove method do not block threads.

The Take method blocks and suspends the current thread in case the queue is empty until data is added to the queue.

These 3 methods internally call the Notfull.signal method to notify the blocked thread that is waiting for the queue to be full.

Linkedblockingqueue

Linkedblockingqueue is a blocking queue that uses a linked list to complete a queue operation. A linked list is a one-way list, not a doubly linked list.

The internal use of the lock and the lock, the two locks to implement blocking ("algorithm").

It comes with the following properties:

// 容量大小private final int capacity;// 元素个数,因为有2个锁,存在竞态条件,使用AtomicIntegerprivate final AtomicInteger count = new AtomicInteger(0);// 头结点private transient Node<E> head;// 尾节点private transient Node<E> last;// 拿锁private final ReentrantLock takeLock = new ReentrantLock();// 拿锁的条件对象private final Condition notEmpty = takeLock.newCondition();// 放锁private final ReentrantLock putLock = new ReentrantLock();// 放锁的条件对象private final Condition notFull = putLock.newCondition();

Arrayblockingqueue only has 1 locks, only 1 are executed when adding data and deleting data, and parallel execution is not allowed.

While Linkedblockingqueue has 2 locks, locks and locks, adding data and deleting data can be done in parallel, of course, when adding data and deleting data, only 1 threads are executed separately.

The addition of data

Linkedblockingqueue has a number of different methods for adding data, add, offer, put method.

The Add method internally calls the Offer method:

public boolean offer(E e) {    if (e == null) throw new NullPointerException(); // 不允许空元素    final AtomicInteger count = this.count;    if (count.get() == capacity) // 如果容量满了,返回false        return false;    int c = -1;    Node<E> node = new Node(e); // 容量没满,以新元素构造节点    final ReentrantLock putLock = this.putLock;    putLock.lock(); // 放锁加锁,保证调用offer方法的时候只有1个线程    try {        if (count.get() < capacity) { // 再次判断容量是否已满,因为可能拿锁在进行消费数据,没满的话继续执行            enqueue(node); // 节点添加到链表尾部            c = count.getAndIncrement(); // 元素个数+1            if (c + 1 < capacity) // 如果容量还没满                notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满        }    } finally {        putLock.unlock(); // 释放放锁,让其他线程可以调用offer方法    }    if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据        signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费    return c >= 0; // 添加成功返回true,否则返回false}

Put method:

public void put(E e) throws InterruptedException {    if (e == null) throw new NullPointerException(); // 不允许空元素    int c = -1;    Node<E> node = new Node(e); // 以新元素构造节点    final ReentrantLock putLock = this.putLock;    final AtomicInteger count = this.count;    putLock.lockInterruptibly(); // 放锁加锁,保证调用put方法的时候只有1个线程    try {        while (count.get() == capacity) { // 如果容量满了            notFull.await(); // 阻塞并挂起当前线程        }        enqueue(node); // 节点添加到链表尾部        c = count.getAndIncrement(); // 元素个数+1        if (c + 1 < capacity) // 如果容量还没满            notFull.signal(); // 在放锁的条件对象notFull上唤醒正在等待的线程,表示可以再次往队列里面加数据了,队列还没满    } finally {        putLock.unlock(); // 释放放锁,让其他线程可以调用put方法    }    if (c == 0) // 由于存在放锁和拿锁,这里可能拿锁一直在消费数据,count会变化。这里的if条件表示如果队列中还有1条数据        signalNotEmpty(); // 在拿锁的条件对象notEmpty上唤醒正在等待的1个线程,表示队列里还有1条数据,可以进行消费}

Linkedblockingqueue Add data methods Add,put,offer like Arrayblockingqueue, the difference is that their underlying implementations are not the same.

When data is blocked in Arrayblockingqueue, consumption data is needed to wake up.

When the data block in the Linkedblockingqueue, because it has 2 locks inside, can be executed in parallel to the data and consumption data, not only when the consumption of data to wake up to insert the blocked thread, while at the time of insertion if the capacity is not full, will also wake up the inserted blocking thread.

Deletion of data

Linkedblockingqueue has several different methods of data deletion, poll, take, and remove methods.

Poll Method:

public E poll() {    final AtomicInteger count = this.count;    if (count.get() == 0) // 如果元素个数为0        return null; // 返回null    E x = null;    int c = -1;    final ReentrantLock takeLock = this.takeLock;    takeLock.lock(); // 拿锁加锁,保证调用poll方法的时候只有1个线程    try {        if (count.get() > 0) { // 判断队列里是否还有数据            x = dequeue(); // 删除头结点            c = count.getAndDecrement(); // 元素个数-1            if (c > 1) // 如果队列里还有元素                notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费        }    } finally {        takeLock.unlock(); // 释放拿锁,让其他线程可以调用poll方法    }    if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据        signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据                return x;}

Take method:

public E take() throws InterruptedException {    E x;    int c = -1;    final AtomicInteger count = this.count;    final ReentrantLock takeLock = this.takeLock;    takeLock.lockInterruptibly(); // 拿锁加锁,保证调用take方法的时候只有1个线程    try {        while (count.get() == 0) { // 如果队列里已经没有元素了            notEmpty.await(); // 阻塞并挂起当前线程        }        x = dequeue(); // 删除头结点        c = count.getAndDecrement(); // 元素个数-1        if (c > 1) // 如果队列里还有元素            notEmpty.signal(); // 在拿锁的条件对象notEmpty上唤醒正在等待的线程,表示队列里还有数据,可以再次消费    } finally {        takeLock.unlock(); // 释放拿锁,让其他线程可以调用take方法    }    if (c == capacity) // 由于存在放锁和拿锁,这里可能放锁一直在添加数据,count会变化。这里的if条件表示如果队列中还可以再插入数据        signalNotFull(); // 在放锁的条件对象notFull上唤醒正在等待的1个线程,表示队列里还能再次添加数据    return x;}

Remove method:

public boolean remove(Object o) {    if (o == null) return false;    fullyLock(); // remove操作要移动的位置不固定,2个锁都需要加锁    try {        for (Node<E> trail = head, p = trail.next; // 从链表头结点开始遍历             p != null;             trail = p, p = p.next) {            if (o.equals(p.item)) { // 判断是否找到对象                unlink(p, trail); // 修改节点的链接信息,同时调用notFull的signal方法                return true;            }        }        return false;    } finally {        fullyUnlock(); // 2个锁解锁    }}

The Linkedblockingqueue take method is blocked for no data, the poll method deletes the link header node, and the Remove method deletes the specified object.

It is important to note that the Remove method requires 2 locks at the same time because the location of the data to be deleted is indeterminate.

Java Blocking Queue Implementation principle analysis-arrayblockingqueue and Linkedblockingqueue

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.