单例模式几种实现方式

1、饿汉式:静态常量

     特点:单例的实例被声明成static和final变量了,在第一次加载类到内存中时就会初始化,所以会创建实例本身是线程安全的

public class Singleton {
    private final static Singleton instance = new Singleton();  
    private Singleton(){
        
    }  
    public static Singleton getInstance(){  
        return instance;  
    }  
}

2、懒汉式:线程不安全

     特点:使用了懒加载模式,但是却存在致命的问题。当多个线程并行调用getInstance()的时候,就会创建多个实例,即在多线程下不能正常工作

public class Singleton {
    private static Singleton instance = null;  
    private Singleton(){
        
    }  
    public static Singleton getInstance(){ 
        if(null == instance) {
            instance = new Singleton();
        }
        return instance;  
    }  
}

3、懒汉式:线程安全

     特点:线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用getInstance()方法,但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时

public class Singleton {
    private static Singleton instance = null;  
    private Singleton(){
        
    }  
    public static synchronized Singleton getInstance(){ 
        if(null == instance) {
            instance = new Singleton();
        }
        return instance;  
    }  
}

4、懒汉式:静态内部类

    特点:使用JVM本身机制保证了线程安全问题;由于SingleHolder是私有的,除了getInstacne()之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖JDK版本

public class Singleton {
    
    private Singleton(){
        
    }
    
    private static class SingleHolder{
        private static final Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance(){ 
        return SingleHolder.instance; 
    }  
}

5、双重检查锁

     特点:是一种使用同步块加锁的方法。又称其为双重检查锁,因为会有两次检查instance == null,一次是在同步块外,一次是在同步快内。为什么在同步块内还要检验一次,因为可能会有多个线程一起进入同步块外的if,如果在同步块内不进行二次检验   的话就会生成多个实例了

public class Singleton {
    //volatile 防止指令重排序
    private static volatile Singleton instance;
    //双重锁
    public static Singleton getInstance() {
        if(null == instance) {
            synchronized(Singleton.class){
                if(null == instance) {
                    instance = new Singleton();
                }
                return instance;
            }
        }
        return instance;
    }
}

问题:这样的写法在很多平台和优化编译器上是错误的。

原因在于:instance = new Singleton ()并非是原子操作,事实上在JVM中这句话做了三件事:

1.instance  = 给新的实体分配内存

2.调用Singleton的构造函数来初始化instance的成员变量

3.将instance对象指向分配的空间(执行完这一步instance就为null)

现在想象一下有线程A和B在调用getInstance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是instance  已经不是null了(内存已经分配),于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,A还没有来得及执行步骤2来完成instance的初始化。

当然编译器也可以这样实现:

1. temp = 分配内存

2. 调用temp的构造函数

3. instance = temp

如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义。

双检锁对于基础类型(比如int)适用。很显然吧,因为基础类型没有调用构造函数这一步。

6、枚举

     特点:通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全,而且还能防止反序列化导致重新创建新的对象

public class Singleton {
    public enum EasySingleton{  
        INSTANCE;  
    }  
}
原文地址:https://www.cnblogs.com/cherish010/p/8566659.html