本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
Java 又改了,这次是 JEP 502 稳定值 Stable Values 改为 JEP 526 惰性常量 Lazy Constants 了。
这意味着什么呢?意味着我半年前写的一篇文章《https://mp.weixin.qq.com/s/Y6P08Yzer06Wehj7dc9qcw》,部分内容失效了,其中的类名等做了一些变更。
我看了一下这个https://openjdk.org/jeps/526内容,这些改动被 JDK 社区说的冠冕堂皇的,总之就是言之有理。所以,接下来我们就一起来一次 Java 26 新特性深度解析,从 Stable Values 到 Lazy Constants,单例模式迎来的革新!
延迟初始化的困境
众所周知,在 Java 开发中,我们经常会遇到一个经典矛盾,启动性能 vs 运行时性能。
比如,一个大型企业级应用,启动时需要初始化数十个服务组件。
class Application {
static final OrderController ORDERS = new OrderController();
static final ProductRepository PRODUCTS = new ProductRepository();
static final UserService USERS = new UserService();
// ... 还有几十个
}
每个组件的构造可能涉及数据库连接、配置加载、资源分配等耗时操作,导致应用启动缓慢。但很多时候,某些组件可能在整个应用生命周期中都不会被使用,我们却已经为它们付出了启动时间成本。
此时,推荐的做法或传统的解决方案是使用延迟初始化。
class OrderController {
private Logger logger = null;
Logger getLogger() {
if (logger == null) {
logger = Logger.create(OrderController.class);
}
return logger;
}
}
但这也带来了新问题。
- 线程安全问题:多线程环境下可能创建多个实例
- 性能损失:失去
final字段的常量折叠优化 - 代码丑陋:需要显式判空,增加维护成本
为此,我们可能会使用单例模式,但是要知道单例模式的写法高达 7 种,面试时常被吊打。单例虽然能解决线程安全问题,但是每次可能都有 if 判断,还有无法利用 final 特性,引入代码复杂性等。
于是,JDK 25 引入了Stable Values和 JDK 26 进化的Lazy Constants,就是为了解决这些困境而生。
JEP 502 vs JEP 526
从 JEP 502 Stable Values 到 JEP 526 Lazy Constants,本质是一次重要的 API 重塑。
JDK 25 的 Stable Values
Stable Values 是 JDK 25 的一次尝试,是一个预览特性。
JEP 502 在 JDK 25 中首次引入StableValue API。
class OrderController {
private final StableValue<Logger> logger = StableValue.of();
Logger getLogger() {
return logger.orElseSet(() ->
Logger.create(OrderController.class));
}
}
这个设计解决了上面提到的一些核心问题,但 Java 社区反馈暴露了一些不足。
- API 层级偏低:
orElseSet()等操作过于贴近底层实现 - 命名不够直观:“StableValue”没有清晰传达“延迟初始化常量”的意图
- 使用方式繁琐:需要每次都调用
orElseSet()方法
所以,JDK 26 进行了改版。
JDK 26 的 Lazy Constants
JDK 26 听取了建议,对这个特性进行了革新,也就是我们现在看到的 Lazy Constants,相当于是第二次预览。
这次预览是基于开发者的反馈,JEP 526 进行了重大调整,可以概括为做减法。
首先是,API 核心重命名。
// JDK 25
StableValue<Logger> logger = StableValue.of();
// JDK 26
LazyConstant<Logger> logger = LazyConstant.of(() ->
Logger.create(OrderController.class));
着背后的考量是,LazyConstant 延迟常量比 StableValue 稳定值更准确地描述了特性本质,这是一个会被延迟初始化但之后不可变的常量。
其次是移除底层操作,聚焦高层用例。
JDK 26 移除了以下容易误用的方法。
orElseSet(Supplier),需要手动调用初始化setOrThrow(T),底层设置操作trySet(T),尝试设置操作
为什么要这样做呢?因为社区认为这些操作要求开发者理解底层同步机制,容易引发竞态条件。所以,JDK 26 改为强制在构造时提供计算函数,将线程安全完全封装在 API 内部。
接下来是集合 API 的深度集成。将集合相关工厂方法迁移到标准接口。
// JDK 25
StableValue.list(size, creator)
// JDK 26
List.ofLazy(size, creator) // 更自然的 API
Map.ofLazy(keySet, creator) // 新增对 Map 的支持
这样做的好处是,提升 API 可发现性,符合 Java 开发者使用习惯。
最后是禁止 null 值。JDK 26 明确禁止计算函数返回 null,这与ScopedValue等现代 API 保持一致,简化实现并提升性能。
Lazy Constants 的核心优势
首先是线程安全的单例初始化。
class Application {
static final LazyConstant<OrderController> ORDERS =
LazyConstant.of(OrderController::new);
public static OrderController orders() {
return ORDERS.get(); // 线程安全,仅初始化一次
}
}
以后的单例,与 synchronized、volatile 等可能就无关了。要知道,这其中比较关键的是:
get()方法保证恰好一次初始化- 无需
synchronized,也无需volatile - 即使在高并发场景下也安全
其次是,JVM 常量折叠优化。
当LazyConstant存储在final字段中且被初始化后,JIT 可以将其内容视为真正的常量,进行以下优化。
- 内联展开:直接嵌入常量值
- 死码消除:基于常量值删除不可达代码
- 简化计算:编译时完成常量表达式求值
这与 JDK 内部使用的@Stable注解效果相同,但现在是安全、公开的 API。
接着是延迟初始化链。
class OrderController {
// Logger在首次使用时才初始化
private final LazyConstant<Logger> logger =
LazyConstant.of(() -> Logger.create(OrderController.class));
void submitOrder(User user, List<Product> products) {
logger.get().info("Order submitted");
}
}
class Application {
// OrderController在首次使用时才初始化
static final LazyConstant<OrderController> ORDERS =
LazyConstant.of(OrderController::new);
}
整个依赖链都是按需初始化,极大改善启动性能。但是,启动速度加快了之后,首次访问就会相对的更慢一些。
五种单例模式的新实现
有了 JDK 26,以前的 7 种单例实现,可以改为现在的 5 种单例实现了。当然,以前的 7 中单例实现,也不是不能用。合起来就是 12 种实现了。
接下来,我们就一起看看新的五种单例实现。
静态字段
社区是非常推荐这种静态字段的单例模式的。
public class DatabaseService {
private static final LazyConstant<DatabaseService> INSTANCE =
LazyConstant.of(DatabaseService::new);
public static DatabaseService getInstance() {
return INSTANCE.get();
}
private DatabaseService() {
// 昂贵的初始化操作
connectToDatabase();
}
}
这种写法的优点是,简洁、线程安全、性能最优,且适合绝大多数单例场景。
枚举单例的增强版
第二种方式,还是使用枚举,同时结合LazyConstant。
public enum ConfigManager {
INSTANCE;
private final LazyConstant<Config> config =
LazyConstant.of(() -> loadConfigFromFile());
public Config getConfig() {
return config.get();
}
}
这种写法的优点是,天然防止反射攻击,配置延迟加载。
带参数的单例池
一次实现或批量实现单例池,用法如下。
public class CacheService {
private final String cacheName;
private static final Map<String, LazyConstant<CacheService>> INSTANCES =
Map.ofLazy(Set.of("user", "product", "order"),
name -> new CacheService(name));
public static CacheService getInstance(String name) {
return INSTANCES.get(name);
}
private CacheService(String cacheName) {
this.cacheName = cacheName;
initializeCache();
}
}
这是 JDK 26 新增的写法,通过Map.ofLazy()实现带参数的单例池。
资源池化
接下来,针对各种连接池,可以进行资源池化了。
class ConnectionPool {
static final List<LazyConstant<Connection>> POOL =
List.ofLazy(10, _ -> createConnection());
public static Connection getConnection() {
long index = Thread.currentThread().threadId() % 10;
return POOL.get((int)index).get();
}
}
这个适用场景就比较多了,数据库连接池、线程池等资源管理都可以采取这种写法。
ServiceLoader 集成
与 ServiceLoader 的集成后续也将非常常用。而且,某些自定义的 Spring bean 也可能会用上延迟加载。从而让 Java 应用的启动速度大幅增加。
public class MyServiceProvider implements MyService {
private static final LazyConstant<MyServiceProvider> INSTANCE =
LazyConstant.of(MyServiceProvider::new);
// ServiceLoader会调用此方法
public static MyServiceProvider provider() {
return INSTANCE.get();
}
}
性能对比:数据说话
| 实现方式 | 首次调用开销 | 后续调用开销 | 线程安全 | 常量折叠 | 代码复杂度 |
|---|---|---|---|---|---|
| 饿汉式 | 无 | 最低 | 安全 | 支持 | 低 |
| 双重检查锁定 | 中等 | 低 | 安全 | 不支持 | 高 |
| 静态内部类 | 无 | 最低 | 安全 | 支持 | 中 |
| LazyConstants | 极低 | 最低 | 安全 | 支持 | 极低 |
根据 OpenJDK 官方数据微基准测试结果表明:
- 相比
volatile双重检查锁定,LazyConstants后续调用性能提升 15-30% - 启动时未使用的组件,节省初始化时间可达 100%(完全避免)
使用注意事项
根据社区的反馈,整体来说,使用这个特性我总结了 4 条注意事项,供大家参考。
预览特性启用
目前还是一个预览特性,不过后续应该是没啥改动了,预计 JDK 26 或 27 可能会被彻底放开。
# 编译
javac --release 26 --enable-preview Main.java
# 运行
java --enable-preview Main
final字段的重要性
直接看下面的用法吧。
// 正确:能触发常量折叠
static final LazyConstant<Config> CONFIG = LazyConstant.of(...);
// 错误:失去优化效果
static LazyConstant<Config> config = LazyConstant.of(...);
避免在计算函数中抛出异常
这个和写单例一样,不建议在创建单例时直接抛出异常。
// 不推荐
LazyConstant.of(() -> {
if (somethingWrong) throw new RuntimeException();
return value;
});
// 推荐
LazyConstant.of(() -> {
try {
return computeValue();
} catch (Exception e) {
// 提供默认值
return DEFAULT_VALUE;
}
});
限制反射修改
目前实例 final 字段仍可通过反射修改,这会破坏常量折叠。但static final字段是安全的。
总结
JEP 526 标志着 Java 延迟初始化 API 的成熟。未来我们可能看到:
- 语言级支持:如
lazy final字段声明 - 完整性提升:限制反射对 final 字段的修改(JEP 500)
- 更广泛的 JVM 优化:基于 Project Valhalla 的值类型集成
Lazy Constants不是简单的 API 重命名,而是一次重要的设计重塑。
- 更清晰的语义:从底层机制描述转向使用场景描述
- 更安全的 API:移除易错操作,强制构造时提供计算逻辑
- 更友好的开发体验:与集合 API 深度集成
对于 Java 开发者而言,这意味着。
- 单例模式实现更简单:一行代码解决线程安全和性能问题
- 应用启动更快:按需初始化,避免不必要的开销
- 代码更易维护:意图明确,减少 boilerplate
未来在项目中采用 LazyConstants 替代传统的双重检查锁定和静态内部类实现,甚至是 Spring bean 的整体延迟初始化实现,享受现代 Java 带来的性能与简洁。

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!