今天继续在看《JCIP》这本书。关于线程安全这里有说一个名词this escape,叫作 this 引用逃逸。它举了一个例子,但是不是很理解,给的例子如下
public class ThisEscape {
public ThisEscape(EventSource eventSource) {
eventSource.registerListener() {
new EventListener() {
public void handleEvent(Object event) {
doSomething(e);
}
}
}
}
}
之后去问了下 deepseek,它给了我一个 Runnable 的例子,我看到这个一下就理解了。
测试程序
public class InnerClassDemo {
private String name;
class innerClass {
Runnable task = () -> {
System.out.printf(name);
};
}
}
是的,平时在重写 run 方法时,会很平常地去使用类中的成员变量,想当然的可以去用,因为都在同一个类中。
但是并没有去想过内部类去访问外部类的属性时还有这一步。
上面这个例子并非 this 逃逸的例子,而是内部类访问外部类隐式拿到内部类的一个步骤。

一、内部类访问外部类
public class OuterClassDemo {
private String name;
private int value = 100;
/**
* 内部类:隐式持有外部类 OuterClassDemo 的引用
* 这个引用可以通过 OuterClassDemo.this 显式访问
*/
class InnerClass {
/**
* 内部类可以直接访问外部类的私有字段
* 这是因为内部类隐式持有了外部类的引用
*/
public void accessOuterFields() {
// 方式 1:直接访问外部类字段(隐式使用 OuterClassDemo.this)
System.out.println("直接访问外部类字段 name: " + name);
System.out.println("直接访问外部类字段 value: " + value);
// 方式 2:显式使用 OuterClassDemo.this 访问外部类引用
System.out.println("外部类引用:" + OuterClassDemo.this);
System.out.println("通过显式引用访问 name: " + OuterClassDemo.this.name);
System.out.println("通过显式引用访问 value: " + OuterClassDemo.this.value);
}
/**
* 在 lambda 表达式中访问外部类字段
* lambda 也会持有外部类的引用
*/
Runnable task = () -> {
System.out.println("Lambda 中访问外部类字段 name: " + name);
System.out.println("Lambda 中访问外部类引用:" + OuterClassDemo.this);
};
/**
* 获取外部类引用
* 用于验证内部类确实持有外部类引用
*/
public OuterClassDemo getOuterInstance() {
return OuterClassDemo.this;
}
/**
* 修改外部类字段
* 证明内部类可以通过隐式引用修改外部类状态
*/
public void modifyOuterField() {
OuterClassDemo.this.name = "被内部类修改";
OuterClassDemo.this.value = 200;
}
}
public static void main(String[] args) {
System.out.println("========== 演示内部类隐式持有外部类引用 ==========\n");
// 创建外部类实例
OuterClassDemo outer1 = new OuterClassDemo();
outer1.name = "外部类实例 1";
outer1.value = 100;
// 创建外部类实例 2
OuterClassDemo outer2 = new OuterClassDemo();
outer2.name = "外部类实例 2";
outer2.value = 200;
System.out.println("1. 创建外部类实例:");
System.out.println(" outer1: " + outer1);
System.out.println(" outer2: " + outer2);
// 从 outer1 创建内部类实例
OuterClassDemo.InnerClass inner1 = outer1.new InnerClass();
// 从 outer2 创建内部类实例
OuterClassDemo.InnerClass inner2 = outer2.new InnerClass();
System.out.println("\n2. 从不同外部类实例创建内部类:");
System.out.println(" inner1: " + inner1);
System.out.println(" inner2: " + inner2);
System.out.println("\n3. 验证内部类持有的外部类引用:");
System.out.println(" inner1 持有的外部类引用:" + inner1.getOuterInstance());
System.out.println(" inner2 持有的外部类引用:" + inner2.getOuterInstance());
System.out.println(" inner1 的引用 == outer1? " + (inner1.getOuterInstance() == outer1));
System.out.println(" inner2 的引用 == outer2? " + (inner2.getOuterInstance() == outer2));
System.out.println("\n4. 内部类访问外部类字段(隐式引用):");
inner1.accessOuterFields();
System.out.println("\n5. Lambda 表达式中的外部类引用:");
inner1.task.run();
System.out.println("\n6. 内部类修改外部类字段(通过隐式引用):");
System.out.println(" 修改前 - outer1.name: " + outer1.name + ", outer1.value: " + outer1.value);
inner1.modifyOuterField();
System.out.println(" 修改后 - outer1.name: " + outer1.name + ", outer1.value: " + outer1.value);
System.out.println("\n7. 验证不同内部类实例持有不同的外部类引用:");
System.out.println(" inner1 访问的 name: " + inner1.getOuterInstance().name);
System.out.println(" inner2 访问的 name: " + inner2.getOuterInstance().name);
System.out.println("\n========== 总结 ==========");
System.out.println("内部类隐式持有外部类引用的关键点:");
System.out.println("1. 内部类在创建时自动持有其外部类实例的引用");
System.out.println("2. 内部类可以直接访问外部类的所有成员(包括私有成员)");
System.out.println("3. 通过 OuterClassDemo.this 可以显式访问外部类引用");
System.out.println("4. 每个内部类实例都绑定到一个特定的外部类实例");
}
}
内部类隐式持有外部类引用的关键点:
- 内部类在创建时自动持有其外部类实例的引用
- 内部类可以直接访问外部类的所有成员(包括私有成员)
- 通过 OuterClassDemo.this 可以显式访问外部类引用
- 每个内部类实例都绑定到一个特定的外部类实例
二、this escape
This escape 的核心问题:
- 在构造函数完成之前,this 引用被暴露给其他线程
- 其他线程可能看到部分初始化的对象状态
- 这违反了 Java 内存模型中的"对象必须在发布前完全构造"的原则