6

According to Java Concurrency in Practice, it is dangerous to start a thread within a class constructor. The reason is that this exposes the this pointer to another thread before the object is fully constructed.

Despite this topic being discussed in many previous StackOverflow questions, I am still having difficulty understanding why this is such a concern. In particular, I hope to seek clarity on whether starting a thread inside a constructor can lead to memory consistency problems from the point of view of the Java memory model.

Let me give you a concrete example of the kind of thing I want to do. (The desired output for this piece of code is for the number 20 to be printed to the console.)

private static class ValueHolder {

    private int value;
    private Thread thread;

    ValueHolder() {
        this.value = 10;
        thread = new Thread(new DoublingTask(this)); // exposing "this" pointer!!!
        thread.start();   // starting thread inside constructor!!!
    }

    int getValue() {
        return value;
    }

    void awaitTermination() {
        try {
            thread.join();
        } catch (InterruptedException ex) {}
    }

}

private static class DoublingTask implements Runnable {

    private ValueHolder valueHolder;

    DoublingTask(ValueHolder valueHolder) {
        this.valueHolder = valueHolder;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName());
        System.out.println(valueHolder.getValue() * 2);  // I expect to print out 20...
    }

}

public static void main(String[] args) {
    ValueHolder myValueHolder = new ValueHolder();
    myValueHolder.awaitTermination();
}

Yes, I know that the thread is started before we return from the constructor. Yes, I know that the this pointer is exposed to the thread. And yet, I am convinced that the code is correct. I believe it will always print the number 20 to the console.

  • The assignment this.value = 10 happens-before thread.start(). (This is because this.value = 10 precedes thread.start() in program order.)
  • The call of thread.start() in the main thread happens-before the start of the .run() method in the newly-created thread. (Because thread.start() is a synchronising action.)
  • The start of the .run() method happens-before the System.out.println(valueHolder.getValue() * 2); print statement. (Again, by program order.)

Therefore, by the Java Memory Model, the print statement should read the correctly-initialised value of valueHolder.value (namely, 10). So despite having ignored the advice from Java Concurrency in Practice, I still appear to have written a correct piece of code.

Have I made a mistake? What am I missing?


UPDATE: Based on the answers and comment, I now understand that my code example is functionally correct for the reasons I provided. However, it is bad practice to write code in this way because there is a chance that other developers will in future add further initialisation statements after the starting of the thread. One situation where such errors are likely is when implementing subclasses of this class.

Kenny Wong
  • 384
  • 3
  • 20
  • 3
    Large software projects will have a lot of maintenance done on them. In such a project, your code *will* be updated by other developers eventually. Right now, a ValueHolder instance is fully initialized when the thread is started, but it would be easy for a future developer to unwittingly add initialization after the thread construction, thus creating a leaked reference to a partially initialized ValueHolder. – VGR May 14 '19 at 14:25
  • 1
    @AndrewTobilko `thread.start()` is a synchronization action. – Andy Turner May 14 '19 at 14:26

1 Answers1

7

Suppose I subclass your class. It may not have initialized its fields by the time they are required.

class BetterValueHolder extends ValueHolder
{
    private int betterValue;

    BetterValueHolder(final int betterValue)
    {
        // not actually required, it's added implicitly anyway.
        // just to demonstrate where your constructor is called from
        super();

        try
        {
            Thread.sleep(1000); // just to demonstrate the race condition more clearly
        }
        catch (InterruptedException e) {}

        this.betterValue = betterValue;
    }

    @Override
    public int getValue()
    {
        return betterValue;
    }
}

This will print zero, regardless of what value is given to the constructor of BetterValueHolder.

Michael
  • 41,989
  • 11
  • 82
  • 128
  • yeah, but this ValueHolder clearly isn't better... – Andrew Tobilko May 14 '19 at 14:19
  • 10
    @AndrewTobilko This is the best code I have ever written. Please don't say these hurtful things. – Michael May 14 '19 at 14:19
  • Thanks very much! A great example! May I ask a follow-up question? Suppose I had decided to initialise `value` inline (i.e. simply declaring `private int value = 10;`). Is my code still correct for the reason I gave? In other words, does the happens-before relationship between the initialisation of value and the start of the thread still hold? – Kenny Wong May 14 '19 at 14:45
  • @Michael Refactor to class MuchBetterValueHolder ;-) – Palamino May 14 '19 at 14:48
  • MuchBetterValueHolderFactory....? – Martin James May 14 '19 at 18:57
  • 1
    @MartinJames You're not [even trying](https://projects.haykranen.nl/java/)! – Michael May 15 '19 at 09:02