As mentioned by Java_author:
5.1.1. Problems with Synchronized Collections
The synchronized collections are thread-safe, but you may sometimes need to use additional client-side locking to guard compound actions.
Example - Multiple producer/consumer problem:
Algorithm using busy wait approach for multiple producers consumers working on thread-unsafe buffer, requires,
global RingBuffer queue; // A thread-unsafe ring-buffer of tasks.
global Lock queueLock; // A mutex for the ring-buffer of tasks.
But below code runs busy wait(while(true){..}
) algorithm using thread safe buffer(queue
), without a lock,
/* NumbersProducer.java */
package responsive.blocking.prodcons;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
public class NumbersProducer implements Runnable{
private BlockingQueue<Integer> numbersQueue;
private final int poisonPill;
private final int poisonPillPerProducer;
public NumbersProducer(BlockingQueue<Integer> numbersQueue, int poisonPill, int poisonPillPerProducer) {
this.numbersQueue = numbersQueue;
this.poisonPill = poisonPill;
this.poisonPillPerProducer = poisonPillPerProducer;
}
@Override
public void run() {
try {
generateNumbers();
}catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void generateNumbers() throws InterruptedException{
for(int i=0; i < 100; i++) {
numbersQueue.put(ThreadLocalRandom.current().nextInt(100));
}
for(int j=0; j < poisonPillPerProducer; j++) {
numbersQueue.put(poisonPill);
}
}
}
/* NumbersConsumer.java */
package responsive.blocking.prodcons;
import java.util.concurrent.BlockingQueue;
public class NumbersConsumer implements Runnable{
private BlockingQueue<Integer> queue;
private final int poisonPill;
public NumbersConsumer(BlockingQueue<Integer> queue, int poisonPill) {
this.queue = queue;
this.poisonPill = poisonPill;
}
public void run() {
try {
while(true) {
Integer number = queue.take();
if(number.equals(poisonPill)) {
return;
}
System.out.println(Thread.currentThread().getName() + " result: " + number);
}
}catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/* Driver.java */
package responsive.blocking.prodcons;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Driver {
public static void main(String[] args) {
int BOUND = 10;
int nProducers = 4;
int nConsumers = Runtime.getRuntime().availableProcessors();
int poisonPill = Integer.MAX_VALUE;
int value = 1;
int poisonPillPerProducer = ((value = nConsumers / nProducers) < 1)?1:value;
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(BOUND);
for(int i =0; i< nProducers; i++) {
new Thread(new NumbersProducer(queue, poisonPill, poisonPillPerProducer)).start();
}
for(int j=0;j < nConsumers; j++ ) {
new Thread(new NumbersConsumer(queue, poisonPill)).start();
}
}
}
Question:
In the above code,
How do I assess the need of additional client-side locking? Key is compound actions...