Java 26:什么是 Lazy Constants,为什么它们让 double-checked locking 失效(JEP 526)

发布: (2026年4月4日 GMT+8 01:28)
8 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将为您翻译成简体中文并保留原有的格式、Markdown 语法以及技术术语。谢谢!

什么是 Java 26 的 JEP 526?

JEP 526 引入了 java.lang.LazyConstant 类:一个保存 不可变 值的容器,最多只会初始化一次。

版本特性名称备注
Java 25Stable Values (JEP 502)API 更冗长,仍在预览中
Java 26Lazy Constants (JEP 526)API 简化,第二次预览
  • final 保证不可变,但要求立即初始化。
  • final 字段允许延迟初始化,但失去并发安全保证。

LazyConstant 结合了两者的优点:你可以在需要时进行初始化,但 仅一次,并拥有与 final 相同的安全性。

为什么存在双重检查锁定以及它的问题?

所有 Java 开发者都写过类似的代码:

public class MetricRegistry {

    private static volatile MetricRegistry instance;

    public static MetricRegistry getInstance() {
        if (instance == null) {
            synchronized (MetricRegistry.class) {
                if (instance == null) {
                    instance = new MetricRegistry();
                }
            }
        }
        return instance;
    }
}

它可以工作,但需要:

  • volatile
  • 两次 null 检查
  • 一个 synchronized

第一次阅读这段代码的人通常会感到困惑。使用静态内部类的替代方案更安全,但仍然是一种 变通办法。这些选项都不是对真实问题的“一等抽象”:“在需要时安全地初始化一次”。

LazyConstant 实际是如何工作的?

想象一个支付服务,它使用一个创建成本很高的 HTTP 客户端,只有在路由被调用时才有意义存在:

public class PaymentService {

    private final LazyConstant httpClient =
        LazyConstant.of(() -> HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(5))
            .build());

    public String charge(String orderId, BigDecimal amount) {
        HttpClient client = httpClient.get();   // 第一次调用会创建客户端
        // 使用 client 调用网关

    }
}
  • HttpClient 在第一次 调用 httpClient.get() 时创建。
  • 随后对 get() 的调用,JVM 会返回同一个值(甚至可能进行 constant‑folding)。
  • 线程安全,无需 synchronizedvolatile

Java 25 到 Java 26 在 JEP 526 中有什么变化?

  • Java 25 – 低层 API(setOrThrowtrySetorElseSet),将资源转化为同步原语。
  • Java 26 – 仅保留 LazyConstant.of(supplier)。你提供初始化函数,JVM 负责其余工作。

“惰性”工厂用于集合

惰性列表和映射的工厂方法位置已更改,从 StableValue 移到直接在 ListMap 上:

// 具有 10 个槽位的惰性列表,每个槽位独立初始化
List processors = List.ofLazy(10, _ -> new OrderProcessor());

// 具有预定义键的惰性映射,值按需初始化
Map limiters = Map.ofLazy(
    Set.of("payments", "refunds", "reports"),
    key -> RateLimiter.create(requestsPerSecond(key))
);

列表的每个槽位和映射的每个条目都有其 独立的初始化周期payments 槽可以存在,而 refunds 尚未创建。

LazyConstant 与普通 final 字段有什么区别?

特性finalLazyConstant
初始化时机在构造函数中(立即)在第一次调用 .get()
同步需求不需要(已经是线程安全的)内部实现线程安全
创建成本总是支付,即使从未使用仅在使用时 支付
灵活性值必须在构造时准备好可以依赖尚未存在的资源
// final 强制立即初始化
private final ExpensiveClient client = new ExpensiveClient(); // 在构造函数中执行

// LazyConstant 在第一次调用 .get() 时初始化
private final LazyConstant<ExpensiveClient> client =
    LazyConstant.of(ExpensiveClient::new);

初始化后,JVM 可以把 LazyConstant 当作常量处理,并应用与 static final 相同的优化。

在 Java 26 中使用 Lazy Constants 前需要了解什么?

  • 仍然是预览版 – 要编译和运行,请启用预览:
javac --enable-preview --release 26 PaymentService.java
java --enable-preview PaymentService
  • LazyConstant 不是 Serializable。如果需要序列化该对象,它将无法工作。
  • 存储的值是 不可变 的,但内部对象可能是可变的。LazyConstant 确保 引用 只被赋值一次;之后对对象的操作由你自行负责。

何时使用(以及何时不使用)LazyConstant

使用 的情形:

  • 对象创建成本高且并非总是必需。
  • 需要线程安全且不想手动管理同步。
  • 示例:数据库连接、HTTP 客户端、配置缓存、度量注册器。

不使用 的情形:

  • 一个普通的 final 就能解决问题。
  • 需要序列化。
  • 想要精确控制初始化时机(例如,在应用启动的某个特定点之前)。

LazyConstant 确保初始化在第一次调用 .get() 之前完成,但不会在你自行选择的任意时刻进行初始化。

为什么这在现在很重要,尽管仍在预览阶段?

double‑checked locking 正在成千上万的 Java 系统中投入生产。并不是因为有人喜欢这种复杂性,而是因为 没有更好的替代方案。JEP 526 是 OpenJDK 对此问题的回应。

该特性在最终确定之前仍可能会改变,第二次预览正是为了收集反馈。然而,方向已经明确:平台希望将“惰性”初始化视为 一等原语,从而消除需要易出错的样板代码模式。

LazyConstant – 一等抽象

使用该特性作为一等抽象,得到编译器和 JVM 的支持,而不是每个开发者自行实现的“代码配方”。现在使用 --enable-preview 进行实验,是在该特性正式稳定之前直接了解其对代码影响的最佳方式。

关于 LazyConstantJEP 526 在 Java 26 中的常见问题

LazyConstant 是线程安全的吗?

是的。唯一的初始化在多线程环境下有效,无需 synchronizedvolatile

我可以将 LazyConstant 与 Serializable 一起使用吗?

不能。如果对象需要序列化,请使用 final 或其他机制。

LazyConstant 能取代使用静态内部类的 Singleton 模式吗?

在大多数情况下可以。结果相同,但代码更简洁,意图在类型上更明确。

使用此特性需要 Java 26 吗?

需要,且必须使用 --enable-preview。JEP 526 仍处于 second preview 阶段,尚未稳定。

如果初始化函数抛出异常会怎样?

异常会传播给调用 .get() 的代码,且 LazyConstant 不会被标记为已初始化。下次调用会再次尝试。

关于作者

我是 Luis De Llamasact digital 的 Developer Advocate,Oracle ACEIBM Champion

如果想了解更多关于 Quarkus、Java 以及生态系统的内容,请在这里找到我:

0 浏览
Back to Blog

相关文章

阅读更多 »

全局变量 VS 局部变量

Java 中的全局变量 Java 不支持真正的全局变量。相反,它使用类级别变量,这些变量的行为类似。类级别变量的类型…

OpenJDK:Panama

Project Panama:连接 JVM 与本机代码 我们正在改进并丰富 Java 虚拟机与定义明确但“外部”的代码之间的连接。