线程在自己的构造函数中调用 this.start() 是否合法?如果是这样,这会导致哪些潜在问题?我知道在构造函数运行完成之前,对象不会完全初始化,但除此之外还有其他问题吗?
7 回答
出于内存安全的原因,您不应将对对象或该对象的字段的引用从其构造函数中暴露给另一个线程。假设您的自定义线程具有实例变量,通过从构造函数中启动它,您肯定会违反 Java 内存模型准则。有关更多信息,请参阅Brian Goetz 的安全施工技术。
如果 Thread 类被进一步子类化,您还会看到奇怪的问题。在这种情况下,一旦 super() 退出,您将最终得到线程已经运行,并且子类在其构造函数中可能执行的任何操作都可能无效。
@bill barksdale 如果线程已经在运行,再次调用 start 会给你一个 IllegalThreadStateException,你不会得到 2 个线程。
我假设您想这样做以使您的代码不那么冗长;而不是说
Thread t = new CustomThread();
t.start();
activeThreads.add(t);
你可以说
activeThreads.add( new CustomThread() );
我也喜欢不那么冗长,但我同意其他受访者的观点,即您不应该这样做。具体来说,它打破了惯例;任何熟悉 Java 并阅读过第二个示例的人都会假设该线程尚未启动。更糟糕的是,如果他们编写自己的线程代码以某种方式与您的交互,那么一些线程将需要调用start
,而其他线程则不需要。
当您自己工作时,这似乎并不引人注目,但最终您将不得不与其他人一起工作,并且养成良好的编码习惯是很好的,这样您就可以轻松地与他人合作并使用标准约定。
但是,如果您不关心约定并且讨厌多余的冗长,那么请继续;即使您start
错误地尝试多次调用,这也不会导致任何问题。
顺便说一句,如果想要更低的冗长并且仍然保持构造函数的“标准”语义,可以创建一个工厂方法:
activeThreads.add( CustomThread.newStartedThread() );
这是合法的,但并不明智。实例的线程部分将被完全初始化,但您的构造函数可能不会。几乎没有理由扩展 Thread,并且使用这样的技巧对您的代码没有帮助。
它是“合法的”,但我认为最重要的问题是:一个班级应该做一件事并做好。
如果您的类在内部使用线程,则该线程的存在不应该在公共 API 中可见。这允许在不影响公共 API 的情况下进行改进。解决方案:扩展 Runnable,而不是 Thread。
如果您的类提供了在这种情况下碰巧在线程中运行的通用功能,那么您不想将自己限制为始终创建线程。这里的解决方案相同:扩展 Runnable,而不是 Thread。
为了减少冗长,我建议使用工厂方法(例如 Foo.createAndRunInThread())。
法律...是的(有其他地方提到的警告)。可取的......不。
我只是一种你很容易避免的气味。如果你想让你的线程自动启动,就像Heinz Kabutz一样。
public class ThreadCreationTest {
public static void main(String[] args) throws InterruptedException {
final AtomicInteger threads_created = new AtomicInteger(0);
while (true) {
final CountDownLatch latch = new CountDownLatch(1);
new Thread() {
{ start(); } // <--- Like this ... sweet and simple.
public void run() {
latch.countDown();
synchronized (this) {
System.out.println("threads created: " +
threads_created.incrementAndGet());
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
};
latch.await();
}
}
}