2

The following code is puzzling me a lot.

import java.util.function.Predicate;

public class Test {

    private final Predicate<String> filter = s -> s != null;

    private boolean started = false;

    private class Runner implements Runnable {
        @Override
        public void run() {
            synchronized ( Test.this ) {
                started = true;
                Test.this.notifyAll();
                traverse("");
            }
        }
    }

    public Test() {
        System.out.println(filter.test(""));
        Thread thread = new Thread(new Runner());
        thread.setDaemon(true);
        thread.start();
    }

    public synchronized String start() {
        while ( !started ) {
            try {
                wait();
            } catch ( InterruptedException ex ) {}
        }
        return "";
    }

    private synchronized void traverse(String s) {
        filter.test(""); // DOES NOT COMPUTE
        filter.test(s);
        System.out.println("not here");
    }

    private static final String STRING = new Test().start(); // POS1

    public static void main(String[] args) {

        System.out.println(STRING); // POS2

    }

}

It gets stuck at DOES NOT COMPUTE. If, however, I delete the line POS1 and alter POS2 to System.out.println(new Test().start()) it runs flawlessly. In above code, filter appears to not evaluate if Test gets initiated though the static variable.

Why is this the case and how can it be fixed please?

dotwin
  • 1,302
  • 2
  • 11
  • 31
  • Set your `started` boolean to be `volatile` and let me know if the same issue occurs. – Jacob G. Feb 21 '19 at 18:44
  • @JacobG. Setting `started` to `volatile` does not help the issue unfortunately. – dotwin Feb 21 '19 at 18:52
  • Was just curious, thanks for letting me know. Other than that, I imagine your program is becoming deadlocked somehow, although I wouldn't see why unless I were able to debug it more and monitor the threads. – Jacob G. Feb 21 '19 at 18:53
  • Thank you. I also put my money on some weird deadlock. I have been debugging this thing for the last 5h without any success and am really hoping for some insights here. – dotwin Feb 21 '19 at 18:56
  • 1
    I can help you more in a few hours if you don't figure it out by then; good luck! – Jacob G. Feb 21 '19 at 18:57

1 Answers1

2

Static field initialization is a part of class initialization. You are waiting(in main thread) during the initialization of static field and not letting class to be marked as initialized. When other thread sees the class state as initialization is in progress by some other thread, It will be blocked until the initialization is completed. And cannot notify the main thread. This causes a deadlock.

As for how to fix it just call it in the main method like you said in the question.

Following lines are from JLS

If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this step.

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2 https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.1.3

miskender
  • 7,460
  • 1
  • 19
  • 23
  • Thank you; I was already having deadlock nightmares. Is there any other way to fix this? The reason I am asking is that this class is public facing and I have no control over its implementation (people may implement `POS1`). How can I prevent this potential deadlock without controlling its implementation? – dotwin Feb 22 '19 at 13:57
  • @dotwin If the code is written this way, deadlock will happen no matter what you do outside. If someone writes the code like this, they are the one who breaks code and should be responsible for it. – miskender Feb 22 '19 at 17:42
  • I agree with your take on this. That said, is there any way to avoid / warn about this deadlock internally? I.e., is there a way to test for this situation (deadlock at `DOES NOT COMPUTE`) and throw an error for example? – dotwin Feb 22 '19 at 21:14
  • 1
    @dotwin You can try static code analyzers, (sonarqube, findbugs etc). Maybe they will catch these types of things. Or you maybe able to define a rule in them. (but if you cant change the code this will be meaningless) For runtime handling I don't think you can do anything. – miskender Feb 22 '19 at 21:31