开发者社区> 问答> 正文

java双重校验锁单例模式是否可以将volatile修饰改为final修饰?

public class Singleton {  
    //是否可以将此处的volatile改为final
    private volatile static Singleton singleton;  

    private Singleton (){}  

    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

最近我在学习多线程入门知识的时候产生了一个这样的疑问,为什么在双重校验锁的单例模式下,使用volatile修饰单例,而不是使用final。

我已经知道了在不使用volatile变量的情况下,对单例成员的初始化可能会重排序至引用赋值之后,从而导致另外一个线程可能误以为单例成员已经初始化完毕,并进行错误的访问。

但我的疑问是,使用final代替volatile可行吗?final不是同样也禁止了这种重排序吗?

另外,是否有简单直观地方式可以展现出,在不使用volatile修饰的情况下可能造成的对未完成初始化的单例对象的异常访问?

展开
收起
游客nb7tzjezepkbe 2023-11-21 19:37:19 94 0
3 条回答
写回答
取消 提交回答
  • 在双重校验锁的单例模式下,使用volatile修饰单例而不是使用final是为了避免指令重排序的问题。

    当使用final修饰单例变量时,虽然可以保证该变量在构造函数执行完毕后不会再被修改,但是无法保证构造函数内部的指令重排序问题。这是因为final关键字只能保证引用的不变性,而无法保证指令的执行顺序。

    而在双重校验锁的单例模式中,需要确保以下两点:

    1. 在第一次调用getSingleton()方法时,才会创建Singleton实例;
    2. 在创建Singleton实例时,要保证线程安全。

    如果不使用volatile修饰单例变量,可能会出现以下情况:

    1. 线程A执行到if (singleton == null)判断语句时,发现singleton为null,进入同步块;
    2. 线程B也执行到if (singleton == null)判断语句时,发现singleton为null,但由于线程A已经获取了锁并进入同步块,线程B只能等待;
    3. 线程A在同步块中创建Singleton实例后,将singleton赋值为新创建的实例;
    4. 线程A离开同步块,继续执行后续代码;
    5. 线程B获得锁并进入同步块,此时singleton已经被赋值为新创建的实例,但是由于指令重排序的问题,线程B可能先执行了if (singleton == null)判断语句,然后才看到singleton已经被赋值为新创建的实例,导致重复创建Singleton实例。

    而使用volatile修饰单例变量可以解决这个问题。volatile关键字可以确保对单例变量的读写操作都是直接从主内存中读取和写入,避免了指令重排序的问题。因此,使用volatile修饰单例变量可以确保双重校验锁的单例模式的正确性。

    2023-11-22 11:59:57
    赞同 展开评论 打赏
  • 面对过去,不要迷离;面对未来,不必彷徨;活在今天,你只要把自己完全展示给别人看。

    不可以将 volatile 修饰符替换为 final 修饰符,这是因为在 Java 中,final 和 volatile 修饰符的作用是不同的。
    final 关键字表示常量,意味着在声明之后不能再修改引用或字段。这会导致双重校验锁失效,因为 getInstance() 方法将永远不会再次执行,从而可能导致多线程问题。
    而 volatile 修饰符可以保证线程可见性,它可以保证线程之间的同步操作。当实例被初始化后,volatile 修饰符可以立即让其他线程看到此变化,从而避免了死循环。
    总之,在实现双重校验锁时,请始终使用 volatile 关键字,以确保实例的正确初始化和线程安全。否则,可能会影响到整个系统的稳定性。

    2023-11-21 21:52:48
    赞同 展开评论 打赏
  • 格物致知

    Java中的双重校验锁单例模式是为了保证线程安全,防止多个线程同时访问导致的数据不一致。在这个模式中,通常会使用volatile关键字来修饰实例变量,以确保变量的可见性。

    volatile关键字的作用是保证变量的可见性,即当一个线程修改了一个volatile变量的值,新值对于其他线程来说是可以立即得知的,不需要等待锁的释放。而final关键字的作用是保证一个类的属性不能被修改,也就是说,一旦一个对象被赋值为final,就不能再对它进行任何修改了。

    所以,如果你想将volatile修饰改为final修饰,那么就需要改变这个模式的实现思路。因为final关键字不能保证变量的可见性,所以你可能需要使用其他的同步机制,比如synchronized关键字或者ReentrantLock,来实现线程安全的单例模式。

    但是,需要注意的是,使用synchronized关键字或者ReentrantLock可能会增加程序的复杂性,并且可能会导致性能下降。所以,在使用这些同步机制的时候,需要权衡利弊,根据实际情况来选择最适合的实现方式。

    2023-11-21 20:46:11
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
Spring Cloud Alibaba - 重新定义 Java Cloud-Native 立即下载
The Reactive Cloud Native Arch 立即下载
JAVA开发手册1.5.0 立即下载