Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
123 views
in Technique[技术] by (71.8m points)

java - Why shouldn't I use Thread.start() in the constructor of my class?

I've been searching for justification as for why you should not call a thread's start method inside a constructor for a class. Consider the following code:

class SomeClass
{
    public ImportantData data = null;
    public Thread t = null;

    public SomeClass(ImportantData d)
    {
        t = new MyOperationThread();

        // t.start(); // Footnote 1

        data = d;

        t.start();    // Footnote 2
    }
}

ImportantData is some generic box of stuff (presumably important) and MyOperationThread is a subclass of thread that knows how to handle SomeClass instances.

Footnodes:

  1. I totally understand why this is unsafe. If the MyOperationThread tries to access SomeClass.data before the following statement finishes (and data is initialized) I'll get an exception that I was otherwise unprepared for. Or maybe I won't. You can't always tell with threads. In any case, I'm setting myself up for weird, unexpected behavior later.

  2. I don't understand why doing it this way is forbidden territory. At this point, all of SomeClass' members have been initialized, no other member functions that change state have been called, and construction is thus effectively finished.

From what I understand, the reason it's considered bad practice to do this is that you can "leak a reference to an object that has not yet been fully constructed." But the object has been fully constructed, the constructor has nothing left to do but return. I have searched other questions looking for a more concrete answer to this question, and have looked into referenced material as well, but haven't found anything that says "you shouldn't because such and such undesirable behavior," only things that say "you shouldn't."

How would starting a thread in the constructor be conceptually different from this situation:

class SomeClass
{
    public ImportantData data = null;

    public SomeClass(ImportantData d)
    {
        // OtherClass.someExternalOperation(this); // Not a good idea

        data = d;

        OtherClass.someExternalOperation(this);    // Usually accepted as OK
    }
}

As another aside, what if the class was final?

final class SomeClass // like this
{
    ...

I saw plenty of questions asking about this and answers that you shouldn't, but none offered explanations, so I figured I'd try to add one that has a few more details.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

But the object has been fully constructed, the constructor has nothing left to do but return

Yes and no. The problem is that according to the Java memory model, the compiler is able to reorder the constructor operations and actually finish the constructor of the object after the constructor finishes. volatile or final fields will be guaranteed to be initialized before the constructor finishes but there is no guarantee that (for example) your ImportantData data field will be properly initialized by the time the constructor finishes.

However as @meriton pointed out in comments, there is a happens before relationship with a thread and the thread that started it. In the case of #2, you are fine because data has to be assigned fully before the thread is started. This is guaranteed according to the Java memory model.

That said, it is considered bad practice to "leak" a reference to an object in its constructor to another thread because if any constructor lines were added after the t.start() it would be a race condition if the thread would see the object full constructed or not.

Here's some more reading:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...