单例模式是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例。
实现单例模式的思路
1外界不能造对象 --把无参构造方法私有
2类本身要造一个 --调用构造方法即可
3通过公共的方式对外提供
--通过public修饰
--又由于无参构造私有,所以要用static修饰符
--为了保证静态方法只能访问静态成员,所以这个对象也要用static修饰
如果这样... static Student s = new Student();
那么外界可以这样... Student.s = null使用为null的对象可能出现空指针异常错误
--所以要加private final修饰
这样想着,就有了下面的这种写法:
这种方法非常简单,因为单例的实例被声明成 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()的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
public class Singleton{
//类加载时就初始化
private static final Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance()方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。
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;
}
}
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中,bean在xml中可以配置为singleton,而且有一个lazy-init属性
lazy-init=true,设置延迟初始化, 在创建容器之后,在第一次从容器获取对象的时候创建单例的对象
如果没有配置或延迟初始化为默认值, 单例的对象会在创建容器的时候创建对象
源码参考:https://gitee.com/constfafa/data_structure_and_algorithm/tree/master/effective-java/src/main/java/rule03
更详细的内容参考大神博客:如何正确地写出单例模式