Reputation: 3012
I wrote a small class that blocks in a method if the value is null. For some reason, it is throwing a StackOverflowError
, what I am doing wrong?
public class BlockingObjectProperty<T> extends SimpleObjectProperty<T> {
public T get() {
if (super.get() == null) {
addListener(((observableValue, t, t1) -> {
synchronized (this) {
notifyAll();
}
}));
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
return super.get();
}
}
Here is my test code:
BlockingObjectProperty<String> blockingObjectProperty = new BlockingObjectProperty<String>();
new Thread(){
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
blockingObjectProperty.set("hello world");
}
}.start();
System.out.println(blockingObjectProperty.get());
And here is a snippet of the exception:
Exception in thread "main" java.lang.StackOverflowError
at com.sun.javafx.binding.ExpressionHelper$SingleChange.<init>(ExpressionHelper.java:144)
at com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:69)
at javafx.beans.property.ObjectPropertyBase.addListener(ObjectPropertyBase.java:87)
at com.neonorb.commons.property.BlockingObjectProperty.get(BlockingObjectProperty.java:8)
at javafx.beans.binding.ObjectExpression.getValue(ObjectExpression.java:50)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.<init>(ExpressionHelper.java:152)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.<init>(ExpressionHelper.java:144)
at com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:69)
at javafx.beans.property.ObjectPropertyBase.addListener(ObjectPropertyBase.java:87)
at com.neonorb.commons.property.BlockingObjectProperty.get(BlockingObjectProperty.java:8)
at javafx.beans.binding.ObjectExpression.getValue(ObjectExpression.java:50)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.<init>(ExpressionHelper.java:152)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.<init>(ExpressionHelper.java:144)
at com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:69)
at javafx.beans.property.ObjectPropertyBase.addListener(ObjectPropertyBase.java:87)
at com.neonorb.commons.property.BlockingObjectProperty.get(BlockingObjectProperty.java:8)
Upvotes: 0
Views: 243
Reputation: 27190
If you want to wait for some variable to become non-null:
private final Object myVarLock = new Object();
private MyType myVar;
MyType get_myVar() {
synchronized(myVarLock) {
while (myVar == NULL) {
myVarLock.wait();
}
return myVar;
}
}
And to set the variable:
void set_myVar(myType newValue) {
synchronized(myVarLock) {
myVar = newValue;
myVarLock.notifyAll();
}
}
NOTES
The getter waits in a loop. This is necessary for strict correctness because the Java Langauge Spec allows wait()
to return even when it has not been notified. (a.k.a., spurious wakeup).
Even if spurious wakeups don't happen in your JVM or in your application, it still is smart to always use a loop. The loop is essential in any algorithm where multiple consumer threads compete with one another to receive events. The loop doesn't cost any more than an if
, so you might as well just be in the habit of always using the loop.
The test of myVar and the assignment of myVar both are inside the synchronized
blocks. This is important. If they weren't both synchronized then here is what could happen:
Thread A enters the getter, tests myVar
and finds that it equals NULL.
Thread B enters the setter, sets myVar
non-null, calls myVarLock.notifyAll()
, returns. The notification is lost because no other thread was waiting for it.
Thread A calls myVarLock.wait()
and waits forever, for an event that is never going to happen again.
Upvotes: 0
Reputation: 33000
When you call addListener
JavaFX asks the property for its current value (in ExpressionHelper.java:152), calling getValue()
again. Then - since the value is still null - you add another listener and so on ad infinitum.
Upvotes: 2