问题
I am trying to design a class as a Code Kata for myself that has a value property that can be set, and the class can issue ValueListener instances. The idea is that there is one instance of ValueHolder with many client threads accessing it concurrently. Each client thread has requested a ValueWatcher and has called waitForValue().
What I am really struggling with is what condition I should use on the while loop around the wait() to avoid spurious notifications (i.e. value hasn't changed). I can see that this design may make it possible of ValueWatcher instances to miss updates, but am less worried about that at this stage.
Would appreciate any guidance on offer!
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;
}
}
}
}
}
回答1:
Interesting problem. Here's one solution off the top of my head. Have a version number along with the value that is being changed. Whenever the value is updated, the version number is also incremented so the ValueWatcher
objects can then check to see if the version went up meaning a change has happened.
Edit:
I initially had an AtomicLong
but I am stealing the idea of a wrapper object from @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;
}
Also, although spurious wakeups are possible it is important to remember that the text:
Always invoke wait inside a loop that tests for the condition being waited for. Don't assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true.
Is more about race conditions and producer/consumer models than spurious wakeups. See my page here about that.
回答2:
All you really care about is if the value changed after you enter the method and its synchronized block. So take a timestamps of the last time a value has been changed and only continue when the last updated timestamp > then when you entered.
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;
}
回答3:
What about each listener having a BlockingQueue
which it gives to the value-setting thread as part of its registration as a listener? Then, when the value is changed, the value-setting thread simply loops over each of those queues, giving it the new value. You may want to use BlockingQueue.offer
in that loop, so that if one thread isn't yet ready to receive the new value, it won't stop other threads from receiving it.
This may not be the most efficient approach, but it's simple, and the concurrent structure (ie the hard part) is well-tested and maintained for you. And it's not that inefficient, either.
回答4:
private class ValueWatcher {
private int oldValue = 0;
public int waitForValue() {
synchronized(monitor) {
while (value == oldValue) {
monitor.wait();
}
oldValue = value
return oldValue;
}
}
}
回答5:
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();
}
}
}
}
The idea is that you keep track of who's waiting and each watcher has a queue of values that have been set since the last invocation of waitForValue()
. This removes the need to store value
in ValueHolder
, which is a good thing as by the time a ValueWatcher
wakes up, that could have changed several times. The drawback of this method is that as you can see is that creating new watchers will block until the monitor is free.
来源:https://stackoverflow.com/questions/9592992/notify-threads-when-counter-changes