我正在尝试将一个类设计为我自己的Code Kata,它具有可以设置的value属性,并且该类可以发出ValueListener实例。我们的想法是有一个ValueHolder实例,其中许多客户端线程同时访问它。每个客户端线程都请求了一个ValueWatcher并调用了waitForValue()。
我真正在努力的是我应该在wait()周围的while循环中使用什么条件来避免虚假通知(即值没有改变)。我可以看到这个设计可能使ValueWatcher实例错过更新,但在此阶段我不那么担心。
非常感谢任何提供的指导!
public class ValueHolder {
private int value = 0;
private final Object monitor = new Object();
public void setValue(int value) {
synchronized(monitor) {
this.value = value;
monitor.notifyAll();
}
}
ValueWatcher createChangeWatcher() {
return new ValueWatcher();
}
private class ValueWatcher {
public int waitForValue() {
synchronized(monitor) {
while (==== ??? =====) {
monitor.wait();
return value;
}
}
}
}
}
答案 0 :(得分:3)
有趣的问题。这是我头脑中的一个解决方案。拥有版本号以及要更改的值。每当更新值时,版本号也会递增,以便ValueWatcher
对象可以检查版本是否上升意味着发生了更改。
修改强>
我最初有一个AtomicLong
,但我从@John Vint窃取了包装对象的想法。
private final VersionValue versionValue = new VersionValue();
public void setValue(int value) {
synchronized (monitor) {
versionValue.value = value;
versionValue.version++;
monitor.notifyAll();
}
}
private class ValueWatcher {
private long localVersion = 0;
public int waitForValue() {
synchronized (monitor) {
while (true) {
if (localVersion < versionValue.version) {
// NOTE: the value might have been set twice here
localVersion = versionValue.version;
return versionValue.value;
}
monitor.wait();
}
}
}
}
private static class VersionValue {
int value;
long version;
}
此外,尽管虚假的唤醒是可能的,但重要的是要记住文本:
始终在测试等待条件的循环内调用wait。不要假设中断是针对您正在等待的特定条件,或者条件仍然是真的。
更多关于竞争条件和生产者/消费者模型而不是虚假的唤醒。请参阅my page here about that。
答案 1 :(得分:2)
您真正关心的是在输入方法及其同步块后是否更改了值。因此,获取上次更改值的时间戳,并且仅在上次更新的时间戳> 1时继续。然后当你进入。
private final StampedValue stamped = new StampedValue();
public void setValue(int value) {
synchronized (monitor) {
this.stamped.value = value;
this.stamped.lastUpdated = System.currentTimeMillis();
monitor.notifyAll();
}
}
private class ValueWatcher {
public int waitForValue() {
synchronized(monitor) {
long enteredOn = System.currentTimeMillis();
while (enteredOn > stamped.lastUpdated) {
monitor.wait();
}
return stamped.value;
}
}
}
private class StampedValue {
long lastUpdated = System.currentTimeMillis();
int value;
}
答案 2 :(得分:1)
每个具有BlockingQueue
的侦听器如何作为其作为侦听器注册的一部分给予值设置线程?然后,当值更改时,值设置线程将简单地遍历每个队列,为其提供新值。您可能希望在该循环中使用BlockingQueue.offer
,这样如果一个线程尚未准备好接收新值,它将不会阻止其他线程接收它。
这可能不是最有效的方法,但它很简单,并发结构(即硬件)经过充分测试和维护。它也不是那么低效。
答案 3 :(得分:0)
private class ValueWatcher {
private int oldValue = 0;
public int waitForValue() {
synchronized(monitor) {
while (value == oldValue) {
monitor.wait();
}
oldValue = value
return oldValue;
}
}
}
答案 4 :(得分:0)
public class ValueHolder {
private final Object monitor = new Object();
private LinkedList<WeakReference<ValueWatcher>> waiters = new LinkedList<WeakReference<ValueWatcher>>();
public void setValue(int value) {
synchronized (monitor) {
Iterator<WeakReference<ValueWatcher>> it = waiters.iterator();
while (it.hasNext()) {
WeakReference<ValueWatcher> ref = it.next();
if (ref.get() == null)
it.remove();
else
ref.get().waitingList.add(value);
}
monitor.notifyAll();
}
}
ValueWatcher createChangeWatcher() {
ValueWatcher ret = new ValueWatcher();
synchronized( monitor ) {
waiters.add(new WeakReference<ValueWatcher>(ret));
}
return ret;
}
private class ValueWatcher {
private Queue<Integer> waitingList = new LinkedList<Integer>();
public int waitForValue() {
synchronized (monitor) {
while (waitingList.isEmpty()) {
monitor.wait();
}
return waitingList.poll();
}
}
}
}
这个想法是你跟踪谁在等待,每个观察者都有一个自上次调用waitForValue()
以来已设置的值队列。这样就无需在value
中存储ValueHolder
,这在ValueWatcher
唤醒的时候是一件好事,可能会多次更改。这种方法的缺点是,正如您所看到的那样,创建新的观察者将会阻塞,直到显示器空闲。