单例模式
一、介绍
- 单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
二、单例模式实现
- 实现方式
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
- 将默认构造函数设为私有, 防止其他对象使用单例类的
2.1 饿汉式
-
使用私有的静态常量在内部创建
-
示例代码
public class Singleton { //1.本类内部创建对象实例,也可以使用静态代码块创建 private final static Singleton instance = new Singleton(); //2. 构造器私有化, 外部不能能 new private Singleton() { } //3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } }
-
优缺点
- 优点:写法简单,在类加载时就进行了初始化。避免了线程同步的问题
- 缺点:在类加在时就进行了初始化,若这个类从头到尾都没有使用过,会造成内存浪费
-
结论:该方式可以使用,但是有可能会造成内存浪费
2.2 懒汉式(线程不安全)
-
在第一次获取对象的时候创建
-
示例代码
public class Singleton { //1.本类内部创建对象实例 private final static Singleton instance; //2. 构造器私有化, 外部不能能 new private Singleton() { } //3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { if(instance == null){ instance = new Singleton(); } return instance; } }
-
优缺点
- 优点:起到了懒加载的效果,不存在浪费内存
- 缺点:只能在单线程中使用。若在多线程中使用可能会产生多个实例:(线程一进入if方法但是还没执行创建实例时,线程二也在创建实例,这种情况就会产生两个实例)
-
结论:在开发中别用这种方法
2.3 懒汉式(线程安全,同步)
-
在第一次获取对象的时候创建(获取实例对象的方法改成同步的)
-
示例代码
public class Singleton { //1.本类内部创建对象实例 private final static Singleton instance; //2. 构造器私有化, 外部不能能 new private Singleton() { } //3. 提供一个公有的静态同步方法,返回实例对象 public static synchronized Singleton getInstance() { if(instance == null){ instance = new Singleton(); } return instance; } }
-
优缺点
- 优点:起到了懒加载的效果,不存在浪费内存,没有线程安全问题
- 缺点:效率太低,每次获取对象都被加锁
-
结论:在开发中别用这种方法
2.4 双重检查
-
在第一次获取对象的时候创建
-
示例代码
public class Singleton { //1.本类内部创建对象实例 private final static Singleton instance; //2. 构造器私有化, 外部不能能 new private Singleton() { } //3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
-
优缺点
- 优点:起到了懒加载的效果,不存在浪费内存,没有线程安全问题,没有性能问题
-
结论:在开发中推荐使用这种方法
2.5 静态内部类
-
在第一次获取对象的时候创建
-
示例代码
public class Singleton { //1.本类内部创建对象实例 volatile:禁止重排序 private static volatile Singleton instance; //2. 构造器私有化, 外部不能能 new private Singleton() { } //写一个静态内部类,该类中有一个静态属性 Singleton private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } //提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } }
-
优缺点
- 优点:起到了懒加载的效果,不存在浪费内存,没有线程安全问题,没有性能问题。
-
结论:这种方式采用了类装载的机制来保证初始化实例时只有一个线程。在开发中推荐使用这种方法
2.6 枚举
-
在第一次获取对象的时候创建
-
示例代码
public enum Singleton { INSTANCE; public void sayOK() { System.out.println("ok~"); } }
-
优缺点
- 优点:起到了懒加载的效果,不存在浪费内存,没有线程安全问题,没有性能问题,避免反序列化重新创建新的对象
-
结论:在开发中推荐使用这种方法
三、在Spring中的应用
-
依赖注入中使用单例模式。DefaultSingletonBeanRegistry
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized(this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
-
该方法使用的是,双重检查
总结
- 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)