You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

懒初始化引用且写入值恒定时,是否具备线程安全性?

线程安全的懒初始化:无volatile的对象引用可行吗?

首先得说,你的思路有一定合理性——毕竟对象引用的读写在Java里确实是原子操作,而且你的初始化方法是幂等的,就算多个线程重复计算,结果也完全一致。但这里有个很容易被忽略的陷阱,会直接导致代码不是线程安全的:指令重排序引发的对象半初始化问题。

为什么这么说?Java虚拟机为了优化性能,可能会对代码的执行顺序进行重排序。比如在你的代码里,线程A执行views = ListUtil.toUnmodifiableList(viewsListString);之后,JVM可能会把this.views = views;这个赋值操作提前,而此时views指向的List对象还没完成初始化(比如集合内部的元素还没完全赋值)。这时候如果线程B过来检查this.views,会发现它不是null,直接返回这个“半成品”List,后续访问的时候就可能抛出NullPointerException,或者看到不一致的集合状态——这绝对是你不想看到的。

你提到的幂等性只能解决“重复计算结果一致”的问题,但解决不了可见性和半初始化的问题。虽然重复计算不会导致逻辑错误,但半初始化的对象是致命的。

那怎么解决这个问题?给你两个常用的方案:

方案一:volatile + 双重检查锁定

这是标准的线程安全懒初始化方案,虽然看起来有点冗余,但能彻底解决指令重排序和可见性问题。修改后的代码如下:

private volatile List<String> views;

private List<String> getViews(ServletContext servletContext) {
    List<String> localViews = this.views; // 局部变量提升性能
    if (localViews == null) {
        synchronized (this) {
            localViews = this.views;
            if (localViews == null) {
                String viewsListString = servletContext.getInitParameter(
                    "my.views.list.VIEWS_LIST");
                localViews = ListUtil.toUnmodifiableList(viewsListString);
                this.views = localViews;
            }
        }
    }
    return localViews;
}

这里的volatile关键字会禁止JVM对初始化和赋值操作进行重排序,同时保证一个线程对views的修改能立刻被其他线程看到。

方案二:静态内部类Holder模式

如果你觉得双重检查锁不够优雅,也可以利用Java类初始化的线程安全性来实现懒加载,代码更简洁:

private List<String> getViews(ServletContext servletContext) {
    return ViewHolder.getInitializedViews(servletContext);
}

// 静态内部类只会在第一次被访问时初始化,且JVM保证这个过程是线程安全的
private static class ViewHolder {
    private static List<String> views;

    private static List<String> getInitializedViews(ServletContext servletContext) {
        if (views == null) {
            synchronized (ViewHolder.class) {
                if (views == null) {
                    String viewsListString = servletContext.getInitParameter(
                        "my.views.list.VIEWS_LIST");
                    views = ListUtil.toUnmodifiableList(viewsListString);
                }
            }
        }
        return views;
    }
}

这个模式的核心是,静态内部类ViewHolder的初始化只会在第一次调用getInitializedViews时触发,而JVM会保证类初始化过程中的线程安全性,不需要额外的volatile修饰(当然加了也没问题)。

总结一下:你的原始代码因为缺少volatile,无法避免指令重排序带来的半初始化风险,所以不是线程安全的。必须通过上述两种方式之一来修复,才能保证多线程环境下的安全访问。

内容的提问来源于stack exchange,提问作者stiemannkj1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:55:46