1. 概述
本文将探讨一个 Java 并发编程中的经典陷阱:不要在构造函数中启动线程。
我们会先简要介绍 Java 中的“发布(publication)”与“逃逸(escape)”概念,然后结合线程启动的场景,说明为什么这种写法容易踩坑。虽然看起来只是一个小细节,但在高并发环境下,它可能导致对象处于未初始化完成的状态就被其他线程访问,从而引发难以排查的 bug。
2. 对象发布与逃逸
✅ 对象发布(Publication):当你把一个对象暴露给外部作用域时,就相当于“发布了”这个对象。比如:
- 将对象返回给调用方
- 赋值给一个
public
静态变量 - 作为参数传递给其他类的方法
❌ 对象逃逸(Escape):如果一个对象在尚未构造完成时就被发布了,那就发生了“逃逸”。这是非常危险的操作,尤其在多线程环境下。
⚠️ 最典型的逃逸场景之一是:**this
引用在构造函数执行期间就泄露了出去**。
一旦 this
被其他线程拿到,而此时对象的字段可能还没初始化完毕,其他线程看到的就是一个“半成品”对象 —— 这会直接破坏线程安全,导致不可预测的行为。
3. 线程启动导致 this 逃逸
🔥 最常见的 this 逃逸方式之一,就是在构造函数里直接启动线程。
来看一个典型反例:
public class LoggerRunnable implements Runnable {
public LoggerRunnable() {
Thread thread = new Thread(this); // this 在这里逃逸了
thread.start();
}
@Override
public void run() {
System.out.println("Started...");
}
}
上面这段代码的问题在于:
this
(即当前正在构造的对象)被传入了Thread
构造器- 线程立即
start()
,意味着另一个线程可能马上开始执行run()
- 此时构造函数还没执行完,对象状态不完整
📌 JVM 并不能保证构造函数的所有指令都在 thread.start()
之前完成。由于指令重排序和内存可见性问题,新线程可能看到一个未初始化完全的对象。
3.1 隐式逃逸也不安全
即使你没有显式传 this
,只要用了匿名内部类,依然可能逃逸:
public class ImplicitEscape {
public ImplicitEscape() {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("Started...");
}
};
t.start();
}
}
⚠️ 虽然看起来没传 this
,但匿名内部类会隐式持有外部类的引用。也就是说,this
依然通过闭包的方式逃逸了。
🎯 结论:
创建线程本身没问题,但在构造函数中直接调用 start()
是高风险操作,无论是显式还是隐式传递 this
,都可能导致对象状态不一致。
3.2 安全替代方案
✅ 正确做法是:延迟线程的启动,确保对象构造完成后再暴露给其他线程。
推荐做法:提供一个独立的 start()
方法。
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(); // ✅ 构造完成后再启动
}
}
使用方式:
SafePublication publication = new SafePublication();
publication.start(); // 对象已完全构造,安全启动
📌 这样做虽然 this
仍然被传给了 Thread
,但关键区别在于:
thread.start()
是在构造函数返回之后才调用的- 此时对象已“安全发布”,不会出现部分初始化的问题
4. 总结
- ❌ 禁止在构造函数中直接
start()
线程,否则this
可能逃逸 - ⚠️ 匿名内部类也会导致隐式
this
逃逸 - ✅ 正确姿势:构造函数只做初始化,线程启动交给外部显式调用(如
start()
方法) - 📚 更深入的内容可参考经典书籍《Java Concurrency in Practice》
所有示例代码均已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-concurrency-advanced-3