什么是单例模式
单例模式是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例。
实现单例模式的思路
1. 外界不能造对象 --把无参构造方法私有
2. 类本身要造一个 --调用构造方法即可
3. 通过公共的方式对外提供
--通过public修饰
--又由于无参构造私有,所以要用static修饰符
--为了保证静态方法只能访问静态成员,所以这个对象也要用static修饰
如果这样... static Student s = new Student();
那么外界可以这样... Student.s = null使用为null的对象可能出现空指针异常错误
--所以要加private修饰
这样想着,就有了下面的这种写法:
懒汉式,线程不安全
- public class Singleton {
- private static Singleton instance;
- //外界不能造对象 把无参构造方法私有
- private Singleton (){}
- //通过公共的方式对外提供 通过public修饰
- public static Singleton getInstance() {
- if (instance == null) {
- //类本身要造一个 调用构造方法即可
- instance = new Singleton();
- }
- return instance;
- }
- }
这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance()的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
饿汉式 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()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
懒汉式的三种写法
方法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版本。
懒汉式、饿汉式在spring IOC中的应用
在spring IOC中,bean在xml中可以配置为singleton,而且有一个lazy-init属性
lazy-init=true,设置延迟初始化, 在创建容器之后,在第一次从容器获取对象的时候创建单例的对象
如果没有配置或延迟初始化为默认值, 单例的对象会在创建容器的时候创建对象
更详细的内容参考大神博客:如何正确地写出单例模式