https://juejin.cn/post/6844903778441756680

    1. public class Singleton {
    2. private static volatile Singleton INSTANCE; // volatile
    3. private Singleton() { // private
    4. }
    5. /**
    6. * 双重校验锁
    7. */
    8. public static Singleton getInstance() {
    9. if (INSTANCE == null) { // 两次判断
    10. synchronized (Singleton.class) {
    11. if (INSTANCE == null) {
    12. INSTANCE = new Singleton();
    13. }
    14. }
    15. }
    16. return INSTANCE;
    17. }
    18. }
    19. ==============
    20. public class Singleton {
    21. private static Singleton INSTANCE = new Singleton(); // volatile
    22. private Singleton() { // private
    23. }
    24. public static Singleton getInstance() {
    25. return INSTANCE;
    26. }
    27. }
    28. ==========
    29. public class Singleton {
    30. private Singleton(){
    31. }
    32. public static Singleton getInstance(){
    33. return SingletonHolder.sInstance;
    34. }
    35. private static class SingletonHolder {
    36. private static final Singleton sInstance = new Singleton();
    37. }
    38. }

    如果不加volatile:

    • 给 instance 分配内存
    • 调用Singleton 的构造函数来初始化成员变量
    • 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了) 但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。 我们只需要将 instance 变量声明成 volatile 就可以了。

    Java 里只有 volatile 变量是能实现禁止指令重排的
    synchronized 块里的非原子操作依旧可能发生指令重排

    引用:
    计数器,打印机