1. Overview
1.概述
In this quick tutorial, we’re going to see why we shouldn’t start a thread inside a constructor.
在这个快速教程中,我们将看到为什么我们不应该在构造函数中启动一个线程。
First, we’ll briefly introduce the publication concept in Java and JVM. Then, we’ll see how this concept affects the way we start threads.
首先,我们将简要地介绍Java和JVM中的发布概念。然后,我们将看到这个概念如何影响我们启动线程的方式。
2. Publication and Escape
2.出版和逃亡
Every time we make an object available to any other code outside of its current scope, we basically publish that object. For instance, publishing happens when we return an object, store it into a public reference, or even pass it to another method.
每当我们将一个对象提供给其当前范围之外的任何其他代码时,我们基本上都会发布该对象。例如,当我们返回一个对象,将其存储到一个public reference中,或者甚至将其传递给另一个方法时,发布就会发生。
When we publish an object that we shouldn’t have, we say that the object has escaped.
当我们发布一个不应该有的对象时,我们说这个对象已经逃逸了。
There are many ways that we can let an object reference escape, such as publishing the object before its full construction. As a matter of fact, this is one of the common forms of escape: when the this reference escapes during object construction.
我们有很多方法可以让一个对象的引用逃逸,比如在对象的完整构造之前发布它。事实上,这也是逃逸的常见形式之一。当this引用在对象构造过程中逃脱时。
When the this reference escapes during construction, other threads may see that object in an improper and not fully-constructed state. This, in turn, can cause weird thread-safety complications.
当this 引用在构建过程中逃脱时,其他线程可能会看到该对象处于一个不恰当的、未完全构建的状态。这反过来又会导致奇怪的线程安全问题。
3. Escaping with Threads
3.用丝线逃亡
One of the most common ways of letting the this reference escape is to start a thread in a constructor. To better understand this, let’s consider an example:
让this引用逃脱的一个最常见的方法是在构造函数中启动一个线程。为了更好地理解这一点,让我们考虑一个例子。
public class LoggerRunnable implements Runnable {
public LoggerRunnable() {
Thread thread = new Thread(this); // this escapes
thread.start();
}
@Override
public void run() {
System.out.println("Started...");
}
}
Here, we explicitly pass the this reference to the Thread constructor. Therefore, the newly started thread might be able to see the enclosing object before its full construction is complete. In concurrent contexts, this may cause subtle bugs.
在这里,我们明确地将this 引用传递给Thread 构造器。因此,新启动的线程可能会在其完整构造完成之前看到包围对象。在并发环境中,这可能会导致微妙的错误。
It’s also possible to pass the this reference implicitly:
也可以隐含地传递thisreference。
public class ImplicitEscape {
public ImplicitEscape() {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("Started...");
}
};
t.start();
}
}
As shown above, we’re creating an anonymous inner class derived from the Thread. Since inner classes maintain a reference to their enclosing class, the this reference again escapes from the constructor.
如上所示,我们正在创建一个从Thread派生的匿名内类。由于内类保持了对其包围类的引用,this引用再次从构造函数中逃脱。
There’s nothing inherently wrong with creating a Thread inside a constructor. However, it’s highly discouraged to start it immediately, as most of the time, we end up with an escaped this reference, either explicitly or implicitly.
在构造函数中创建一个Thread本身并没有什么问题。然而,我们非常不鼓励立即启动它,因为大多数时候,我们最终会得到一个转义的this引用,无论是显式还是隐式的引用。
3.1. Alternatives
3.1.替代品
Instead of starting a thread inside a constructor, we can declare a dedicated method for this scenario:
我们可以为这种情况声明一个专门的方法,而不是在构造函数中启动一个线程。
public class SafePublication implements Runnable {
private final Thread thread;
public SafePublication() {
thread = new Thread(this);
}
@Override
public void run() {
System.out.println("Started...");
}
public void start() {
thread.start();
}
};:
As shown above, we still publish the this reference to the Thread. However, this time, we start the thread after the constructor returns:
如上所示,我们仍然将this引用发布到Thread。但是,这一次,我们在构造函数返回后启动线程。
SafePublication publication = new SafePublication();
publication.start();
Therefore, the object reference does not escape to another thread before its full construction.
因此,对象引用不会在其完全构建之前逃到另一个线程。
4. Conclusion
4.总结
In this quick tutorial, after a brief introduction to the safe publication, we saw why we shouldn’t start a thread inside a constructor.
在这个快速教程中,在简单介绍了安全发布后,我们看到了为什么我们不应该在构造函数内启动线程。
More detailed information about the publication and escape in Java can be found in the Java Concurrency in Practice book.
关于Java中的发布和转义的更多详细信息可以在Java Concurrency in Practice书中找到。
As usual, all the examples are available over on GitHub.
像往常一样,所有的例子都可以在GitHub上找到。