My recent phone interview encountered with an OOD question about multi-thread safe queue (and I'm a new grad).
I didn't do well so I read into the source code of ArrayBlockingQueue, which is a widely used multi-thread-safe queue class in openJDK.
For full source code of openJDK, check here.
Class Members
1 | public class ArrayBlockingQueue<E> extends AbstractQueue<E> |
With these members, ArrayBlockingQueue implemented a fixed-size, multi-thread safe queue.
Main methods
For a queue, there're two main write operations: put one element into the queue, and get one element out from the queue. In multi-thread safe programs all write operations should share a Mutex. Here we're implementing our own version of three different adding method and three different polling method. They should be like
Name | Behavior |
---|---|
add() | Add an element to the queue. Return true if succeed; Throw IllegalStateException if the queue is full. |
offer() | Add an element to the queue. Return true if succeed; Return false if failed. |
put() | Add an element to the queue. Return true if succeed; Waiting if the queue is full until it's not full. |
poll() | Retrieves and removes the head of this queue, or returns null if this queue is empty. |
take() | Retrieves and removes the head of this queue, waiting if necessary until an element becomes available. |
The key point here to keep the queue safe is by using a main lock and two conditions. Since all the operations are write operations, they share a Mutex.
Let's look into these methods one by one.
add()
In fact, add simply inherited it's super class's implementation.1
2
3
4
5
6
7public boolean add(E e) {
if (offer(e)) {
return true;
} else {
throw new IllegalStateException("Queue full");
}
}
offer()
1 | /** |
put()
1 | /** |
lock() and lockInterruptibly()
As we can see from the code above, in offer()
, we used lock.lock()
. However in put()
, we used lock.lockInterruptibly()
instead. What is the difference between them?
lock.lock()
would continuously trying to get the lock.lock.lockInterruptibly()
behaves almost the same as lock()
, however, if it is already interrupted or is interrupted while trying to get the lock, it would throw an exception, which should be handled by it's invoker.
poll()
1 | public E poll() { |
take()
1 | public E take() throws InterruptedException { |
insert() and extract()
These two methods basically does two thing: do the operation and notify the awaiting process.
1 | /** |
Above is the main part of the ArrayBlockingQueue in Java (openJDK). It's easy to understand, and a good review of two-condition algorithm.