"queueLock" until we need it again to add the next task. queueLock.release(); } } // Method representing each consumer thread's behavior: public method consumer() { while (true) { // Acquire "queueLock" for the initial predicate check. queueLock.acquire(); // Critical section that checks if the queue is non-empty. while (queue.isEmpty()) { // Release "queueLock", enqueue this thread onto "queueEmptyCV" and sleep this thread. wait(queueLock, queueEmptyCV); // When this thread is awoken, re-acquire "queueLock" for the next predicate check. } // Critical section that takes a task off of the queue (note that we are holding "queueLock"). myTask = queue.dequeue(); // Wake up one or all producer threads that are waiting for the queue to be non-full // now that it is guaranteed, so that a producer thread will add a task. signal(queueFullCV); // Or: broadcast(queueFullCV); // End of critical sections. // Release "queueLock" until we need it again to take the next task. queueLock.release(); // Go off and do something with the task. doStuff(myTask); } } This ensures concurrency between the producer and consumer threads sharing the task queue, and blocks the threads that have nothing to do rather than busy-waiting as shown in the aforementioned approach using spin-locks. A variant of this solution could use a single condition variable for both producers and consumers, perhaps named "queueFullOrEmptyCV" or "queueSizeChangedCV". In this case, more than one condition is associated with the condition variable, such that the condition variable represents a weaker condition than the conditions being checked by individual threads. The condition variable represents threads that are waiting for the queue to be non-full and ones waiting for it to be non-empty. However, doing this would require using broadcast in all the threads using the condition variable and cannot use a regular signal. This is because the regular signal might wake up a thread of the wrong type whose condition has not yet been met, and that thread would go back to sleep without a thread of the correct type getting signaled. For example, a producer might make the queue full and wake up another producer instead of a consumer, and the woken producer would go back to sleep. In the complementary case, a consumer might make the queue empty and wake up another consumer instead of a producer, and the consumer would go back to sleep. Using broadcast ensures that some thread of the right type will proceed as expected