Array BlockingQueue Source Code Analysis of Dead java Sets

Keywords: Java

problem

(1) How is Array BlockingQueue implemented?

(2) Does Array Blocking Queue need to be expanded?

(3) What are the disadvantages of Array Blocking Queue?

brief introduction

Array BlockingQueue is the next block queue implemented in arrays in java Concurrent packages. It is thread-safe. See the following analysis for expansion.

queue

Queues, is a linear table, which is characterized by FIFO, or FIFO, as we usually queue, first come first served, that is, first in the queue first out.

Source code analysis

Main attributes

// Storing elements using arrays
final Object[] items;

// A pointer to an element
int takeIndex;

// Pointer for Placing Elements
int putIndex;

// Element quantity
int count;

// Locks guaranteeing concurrent access
final ReentrantLock lock;

// Non empty condition
private final Condition notEmpty;

// Non full condition
private final Condition notFull;

Through attributes, we can get the following important information:

(1) Storing elements with arrays;

(2) Mark the position of the next operation by placing and fetching the pointer;

(3) Using re-entry lock to ensure concurrent security;

Main construction methods

public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    // Initialize arrays
    this.items = new Object[capacity];
    // Creating reentrant locks and two conditions
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

Through the construction method, we can draw the following two conclusions:

(1) When Array BlockingQueue is initialized, it must pass in capacity, that is, the size of the array;

(2) We can control whether the type of re-entry lock is fair lock or unfair lock by constructing method.

Join the team

There are four ways to join the team. They are add (E), offer (E), put (E), offer (E), long time out (E), Time Unit unit. What's the difference between them?

public boolean add(E e) {
    // Call the add(e) method of the parent class
    return super.add(e);
}

// super.add(e)
public boolean add(E e) {
    // Call offer(e) if it returns true successfully, and throw an exception if it fails
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

public boolean offer(E e) {
    // Elements cannot be empty
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // Lock up
    lock.lock();
    try {
        if (count == items.length)
            // If the array is full, return false
            return false;
        else {
            // If the array is not full, call the queue entry method and return true
            enqueue(e);
            return true;
        }
    } finally {
        // Unlock
        lock.unlock();
    }
}

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // Lock, throw an exception if the thread interrupts
    lock.lockInterruptibly();
    try {
        // If the array is full, use notFull to wait
        // notFull waits, which means that the queue is full now.
        // The queue is not satisfied until one element is removed.
        // Then wake up notFull and continue with the current logic.
        // Here's why you use while instead of if
        // Because it's possible to have multiple threads blocking on lock
        // Even if it wakes up, other threads may modify the queue one step at a time and become full again.
        // It's time to wait again.
        while (count == items.length)
            notFull.await();
        // Join the team
        enqueue(e);
    } finally {
        // Unlock
        lock.unlock();
    }
}

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // Lock up
    lock.lockInterruptibly();
    try {
        // If the array is full, block nanos nanoseconds
        // If there is still no space to wake up the thread and time is up, return false.
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        // Join the team
        enqueue(e);
        return true;
    } finally {
        // Unlock
        lock.unlock();
    }
}

private void enqueue(E x) {
    final Object[] items = this.items;
    // Place the element directly in the pointer position
    items[putIndex] = x;
    // If the pointer is placed at the end of the array, it returns to the head.
    if (++putIndex == items.length)
        putIndex = 0;
    // Quantity plus 1
    count++;
    // Wake up notEmpty, because there's an element in the team, so it's definitely not empty.
    notEmpty.signal();
}

(1) throw an exception if the queue is full when adding (e);

(2) When offer(e), return false if the queue is full;

(3) If the queue is full, use notFull to wait.

(4) When offer(e, timeout, unit) is full, wait for a period of time and return false if the queue is still full.

(5) Store elements by placing pointers and recycling arrays;

Team out

There are four ways to get out. They are remove(), poll(), take(), poll (long time out, Time Unit unit). What's the difference between them?

public E remove() {
    // Call poll() method to queue
    E x = poll();
    if (x != null)
        // If an element is out of line, return the element.
        return x;
    else
        // Throw an exception if no element is out of line
        throw new NoSuchElementException();
}

public E poll() {
    final ReentrantLock lock = this.lock;
    // Lock up
    lock.lock();
    try {
        // If there are no elements in the queue, return null, otherwise queue
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // Lock up
    lock.lockInterruptibly();
    try {
        // If the queue has no elements, the blocking waits on condition notEmpty
        while (count == 0)
            notEmpty.await();
        // Be elementary and get out of the team again
        return dequeue();
    } finally {
        // Unlock
        lock.unlock();
    }
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // Lock up
    lock.lockInterruptibly();
    try {
        // If the queue has no elements, the blocking waits for nanos nanoseconds
        // If the next thread gets a lock but the queue is still elemental and has timed out, it returns null
        while (count == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    // Take the element of the pointer position
    E x = (E) items[takeIndex];
    // Set the pointer position to null
    items[takeIndex] = null;
    // Take the pointer forward, and if the array ends, return to the front end of the array for recycling
    if (++takeIndex == items.length)
        takeIndex = 0;
    // Number of elements minus 1
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // Wake up notFull condition
    notFull.signal();
    return x;
}

(1) If the queue is empty when remove(), an exception is thrown.

(2) When poll() returns null if the queue is empty;

(3) If the queue is empty when take(), it will block and wait on condition notEmpty.

(4) When poll(timeout, unit) is empty, the queue is blocked and waits for a period of time, and then returns null if it is still empty.

(5) Use the pointer loop to extract elements from the array;

summary

(1) Array BlockingQueue does not need to be expanded because it specifies capacity at initialization and recycles arrays;

(2) Array BlockingQueue recycles arrays using takeIndex and putIndex;

(3) Entry and exit methods are defined to meet different purposes.

(4) Using re-entry lock and two conditions to ensure concurrent security;

Egg

(1) On the methods in Blocking Queue?

BlockingQueue is the top-level interface for all blocking queues. It defines a set of methods. What's the difference between them?

operation throw Returns a specific value block overtime
Join the team add(e) offer(e)-false put(e) offer(e, timeout, unit)
Team out remove() poll()-null take() poll(timeout, unit)
inspect element() peek()-null - -

(2) What are the disadvantages of Array Blocking Queue?

(a) The queue length is fixed and must be specified at initialization, so the capacity must be carefully considered before using it;

b) If the speed of consumption does not keep up with the speed of queue entry, the threads of providers will be blocked all the time, and the more blocked, the more dangerous it is.

c) Only one lock is used to control entry and exit, which is inefficient. Is it possible to split the entry and exit into two locks with the help of the idea of segmentation? And listen to the next decomposition.

Welcome to pay attention to my public number "Tong Ge Reads Source Code". Check out more articles about source code series and enjoy the sea of source code with Tong Ge.

Posted by Jamez on Mon, 06 May 2019 01:10:37 -0700