Chris Smith
Chris Smith

Reputation: 3012

Blocking object property

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

Answers (2)

Solomon Slow
Solomon Slow

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

wero
wero

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

Related Questions