Java:java学习笔记之Java单例模式的简单理解和使用

Java单例模式

1、饿汉式单例的实现如下:

//饿汉式实现
public class SingleB {
    
    
    private static final SingleB INSTANCE = new SingleB();
    private SingleB() {
    
    }
    public static SingleB getInstance() {
    
    
        return INSTANCE;
    }
}

2、懒汉式终极版本:volatile

// Version 4 
public class Single4 {
    
    
    private static volatile Single4 instance;
    private Single4() {
    
    }
    public static Single4 getInstance() {
    
    
        if (instance == null) {
    
    
            synchronized (Single4.class) {
    
    
                if (instance == null) {
    
    
                    instance = new Single4();
                }
            }
        }
        return instance;
    }
}

在这里插入图片描述
主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:

  • 1、给 singleton 分配内存
  • 2、调用 Singleton 的构造函数来初始化成员变量,形成实例
  • 3、将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2

  • 如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

再稍微解释一下,就是说,由于有一个『instance已经不为null但是仍没有完成初始化』的中间状态,而这个时候,如果有其他线程刚好运行到第一层if (instance == null)这里,这里读取到的instance已经不为null了,所以就直接把这个中间状态的instance拿去用了,就会产生问题。

  • 这里的关键在于——线程T1对instance的写操作没有完成,线程T2就执行了读操作

volatile关键字的作用

volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。

  • 注意:volatile阻止的不singleton = newSingleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作(if(instance == null))

3、Effective Java 1 —— 静态内部类

// Effective Java 第一版推荐写法
public class Singleton {
    
    
    private static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){
    
    }
    public static final Singleton getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

这种写法非常巧妙:

  • 对于内部类SingletonHolder,它是一个饿汉式的单例实现,在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例。
  • 同时,由于SingletonHolder是一个内部类,只在外部类的SingletongetInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。
  • 它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现。

4、5.2 Effective Java 2 —— 枚举

// Effective Java 第二版推荐写法
public enum SingleInstance {
    
    
    INSTANCE;
    public void fun1() {
    
     
        // do something
    }
}

// 使用
SingleInstance.INSTANCE.fun1();

在这里插入图片描述

参考

1、Hi,我们再来聊一聊Java的单例吧

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/122524092
今日推荐