I have a problem with the following code fragment. It’s intended to handle events (provided via calls on processEvent method) which are added to an event queue (ConcurrentLinkedQueue). Events are added to an event queue and processed periodically in the run method.
All is fine almost always. But sometimes after a call on the processEvent method, when an event is added to the queue, the run part fails to see there is a new event.
Any idea on what is wrong? Besides the obvious mistake in using a String constant as a lock?
import java.util.concurrent.ConcurrentLinkedQueue;
public class MyCommunicator implements Runnable {
private ConcurrentLinkedQueue<MyEvent> eventQueue = null;
private boolean stopped = false;
private String lock = "";
private Thread thread = null;
public MyCommunicator() {
eventQueue = new ConcurrentLinkedQueue<MyEvent>();
}
public void start() {
thread = new Thread(this, "MyCommunicatorThread");
thread.start();
}
public void stop() {
stopped = true;
synchronized (lock) {
lock.notifyAll();
}
eventQueue.clear();
}
public void run() {
while (!stopped) {
try {
MyEvent event = null;
while (!stopped && ((event = eventQueue.peek()) != null)) {
sendEvent(event);
eventQueue.poll();
}
if (!stopped) {
synchronized (lock) {
lock.wait(10000L);
}
}
}
catch (Exception e) {
}
}
}
/**
* START EVENT JOB - ADD A NEW EVENT TO BE PROCESSED
*/
public void processEvent(MyEvent event) {
eventQueue.offer(event);
synchronized (lock) {
lock.notifyAll();
}
}
/**
* END EVENT JOB
*/
private void sendEvent(MyEvent event) {
// do send event job
}
}
You have what’s known as a missed signal. You poll the queue and then wait on the monitor (taking the lock). The producer threads add events and then call
notifyAll()(taking the lock). There is nohappens-beforerelationship between event queuing/poll and the conditional await/notification.It is therefore possible for thread A to poll while empty and then try to acquire the lock, meanwhile thread B adds an element and acquires the lock, notifying all awaiting threads then releasing the lock. Thread A then acquires the lock and awaits it, but the signal has been missed.
As you are using the lock purely for signalling, you might consider another mechanism such as a reusable latch like Doug Lea’s new jdk7 Phaser, or just use a
BlockingQueuedirectly.Alternatively we have a couple of ReusableLatch such as a BooleanLatch for a single reader thread or a PhasedLatch for multi-party support.