所以,我有以下对象(为了举例简化):
public class SomeListener implements EventListener{
public final Object lock = new Object();
public int receivedVal;
@Override
public onDataAvailable(int val){
synchronized(lock){
System.out.println("listener received val: " + val);
receivedVal = val;
lock.notifyAll();
}
}
}
我在主线程的某处(再次,简化)中有这段代码:
SomeListener listener = new SomeListener();
EventGenerator generatorThread = new EventGenerator();
generatorThread.addListener(listener);
synchronize(listener.lock){
generatorThread.start();
listener.lock.wait();
System.out.println("value is: " + listener.receivedVal);
}
//some other stuff here....
现在,EventGenerator对象使用val = 1调用“onDataAvailable”,然后在另一个线程上调用val = 2。基本上,我期望看到的是:
listener received val: 1
value is: 1
listener received val: 2
然而,我通常得到:
listener received val: 1
listener received val: 2
value is: 2
就好像第二次调用“onDataAvailable”在主线程被唤醒之前获取了锁。在“onDataAvailable”的同步块之后的简单println或短暂睡眠足以获得预期的结果,但这似乎是一个丑陋的补丁。
我在这里做错了什么?
注意,我无法控制调用侦听器的线程。它基本上是一个通过网络接收事件的线程。有时它会在同一条消息中收到多个事件,因此会一个接一个地多次调用“onDataAvailable”,这会导致我的问题。其他时候它将在两个不同的消息中接收两个事件,这留下足够的时间让主线程在事件之间唤醒。
答案 0 :(得分:6)
就好像第二次调用“onDataAvailable”在主线程被唤醒之前获取了锁
如果您有多个线程正在调用onDataAvailable(...)
,则可以预料到这一点。调用notifyAll()
时,等待该对象的所有线程都将移动到阻塞队列,但后面队列中已有的任何线程。在继续之前,他们都必须等待lock
同步。
其他时候,它会在两个不同的消息中收到两个事件,这会让主线程有足够的时间在事件之间唤醒。
是的,因此多个网络处理程序线程正在调用onDataAvailable(...)
。第二个在synchronized(lock)
等待它被阻止。调用notifyAll()
时,另一个线程也进入块队列,但在后面的另一个处理程序。
如果只有一个处理程序线程,我会感到惊讶你得到了那个输出。在这种情况下,通知的线程应该在单线程处理程序可以解锁之前获取同步锁,读取另一条消息并再次锁定。
我在这里做错了什么?
问题不在于处理线程的方式,而在于处理receivedVal
的方式。您应该在处理线程中处理值 ,否则您需要将它放在某个同步队列(可能是LinkedBlockingQueue
)中,以便主线程按顺序打印出来。
如果您使用BlockingQueue
,那么主队列只会执行queue.take()
,导致它等待结果,处理程序线程只执行queue.put(...)
。您不需要自己进行wait()
或notifyAll()
来电。
这样的事情会起作用:
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
...
@Override
public onDataAvailable(int val){
System.out.println("listener received val: " + val);
queue.put(val);
}
...
generatorThread.addListener(listener);
generatorThread.start();
while (true) {
// this waits for the queue to get a value
int val = queue.take();
System.out.println("value is: " + val);
}