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.