is full of tasks, then we need the producer threads to block until there is room from consumer threads dequeueing tasks. On the other hand, whenever the queue is empty, then we need the consumer threads to block until more tasks are available due to producer threads adding them. As the queue is a concurrent object shared between threads, accesses to it must be made atomic, because the queue can be put into an inconsistent state during the course of the queue access that should never be exposed between threads. Thus, any code that accesses the queue constitutes a critical section that must be synchronized by mutual exclusion. If code and processor instructions in critical sections of code that access the queue could be interleaved by arbitrary context switches between threads on the same processor or by simultaneously-running threads on multiple processors, then there is a risk of exposing inconsistent state and causing race conditions. Incorrect without synchronization A naïve approach is to design the code with busy-waiting and no synchronization, making the code subject to race conditions: global RingBuffer queue; // A thread-unsafe ring-buffer of tasks. // Method representing each producer thread's behavior: public method producer() { while (true) { task myTask = ...; // Producer makes some new task to be added. while (queue.isFull()) {} // Busy-wait until the queue is non-full. queue.enqueue(myTask); // Add the task to the queue. } } // Method representing each consumer thread's behavior: public method consumer() { while (true) { while (queue.isEmpty()) {} // Busy-wait until the queue is non-empty. myTask = queue.dequeue(); // Take a task off of the queue. doStuff(myTask); // Go off and do something with the task. } } This code has a serious problem in that accesses to the queue can be interrupted and interleaved with other threads' accesses to the queue. The queue.enqueue and queue.dequeue methods likely have instructions to update the queue's member variables such as its size, beginning and ending positions, assignment and allocation of queue elements, etc. In addition, the queue.isEmpty() and queue.isFull() methods read this shared state as well. If producer/consumer threads are allowed to be interleaved during the calls to enqueue/dequeue, then inconsistent state of the queue can be exposed leading to race conditions. In addition, if one consumer makes the queue empty in-between another consumer's exiting the busy-wait and calling "dequeue", then the second consumer will attempt to dequeue from