Java单例模式--饿汉式、懒汉式需要怎么写

什么是单例模式

单例模式是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例。

 

实现单例模式的思路

 1外界不能造对象  --把无参构造方法私有

 2类本身要造一个  --调用构造方法即可

 3通过公共的方式对外提供

       --通过public修饰

       --又由于无参构造私有,所以要用static修饰符

       --为了保证静态方法只能访问静态成员,所以这个对象也要用static修饰

             如果这样...     static Student s = new Student();

             那么外界可以这样...   Student.s = null使用为null的对象可能出现空指针异常错误

       --所以要加private final修饰

这样想着,就有了下面的这种写法:

饿汉式 static final field

这种方法非常简单,因为单例的实例被声明成 static和 final变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}

    public static Singleton getInstance(){
        return instance;
    }
}

这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。

饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

 

懒汉式,线程不安全

public class Singleton {
    
    private static Singleton instance;

     //外界不能造对象 把无参构造方法私有
    private Singleton (){}

    //通过公共的方式对外提供  通过public修饰
    public static Singleton getInstance() {
     if (instance == null) {
         //类本身要造一个  调用构造方法即可
         instance = new Singleton();
     }
     return instance;
    }
}

这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance()的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

 

懒汉式的三种写法

 

方法1:懒汉式线程安全

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance;
    
    private Singleton(){}

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

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance()方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

方式2:双重检验锁

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile 保证编译器不进行优化
    private Singleton (){}

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

方式3:静态内部类

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


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

解释一下:当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,内部类SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本

懒汉式、饿汉式在spring IOC中的应用

在spring IOC中,bean在xml中可以配置为singleton,而且有一个lazy-init属性

lazy-init=true,设置延迟初始化, 在创建容器之后,在第一次从容器获取对象的时候创建单例的对象

如果没有配置或延迟初始化为默认值, 单例的对象会在创建容器的时候创建对象

源码参考:https://gitee.com/constfafa/data_structure_and_algorithm/tree/master/effective-java/src/main/java/rule03
更详细的内容参考大神博客:如何正确地写出单例模式

你可能感兴趣的:(设计模式)