Singleton-单例模式

单例模式

  • 单例模式是一种创建模式,单例类负责自己创建自己的对象并且一个类只有一个实例对象,并且向整个系统提供这个实例.系统可以直接访问这个实例而不需要实例化
  • 单例模式的特点:单例类只有一个实例单例类必须自己创建自身的唯一实例单例类必须给其余系统对象提供创建的唯一实例

单例模式的实现方式

  • 单例模式要保证一个类只有一个实例,并且提供给全局访问,主要用于解决一个全局使用的类频繁创建和销毁的问题,通过判断系统是否存在这个单例来解决这样的问题,如果有这个单例则返回这个单例,否则就创建这个单例,只要保证构造函数是私有的即可保证一个类只有一个实例:将该类的构造方法定义为私有方法即可提供全局一个该实例的访问点:单例类自己创建实例,提供一个静态方法作为实例的访问点即可
  • 饿汉和懒汉比较:懒汉:单例类对象实例懒加载,不会提前创建对象实例,只有在使用对象实例的时候才会创建对象实例饿汉:在单例对象实例进行声明引用时就进行实例化创建对象实例
  • 单例模式除去线程不安全的懒汉,通常有五种实现方式:懒汉双检锁饿汉静态内部类枚举
  • 一般情况下,直接使用饿汉实现单例模式
  • 如果明确要求懒人加载通常使用静态内部类实现单例模式
  • 如果有关于反序列化创建对象会考虑使用枚举实现单例模式

打开网易新闻 查看精彩图片

线程不安全懒汉

  • 单例模式线程不安全懒汉Singleton示例
  • 使用了懒加载模式
  • 存在当多个线程并行调用getInstance()方法时,会创建多个实例的问题.也就是说,在多线程模式下是无法正常工作的

线程安全懒汉

  • 单例模式线程安全懒汉Singleton示例
  • 解决了在多线程环境下创建多个实例的问题
  • 存在每次获取实例都需要申请锁的问题,方法效率低下,因为在任何时候只能有一个线程可以调用getInstance()方法

双检锁

  • 双重检查锁模式:doule checked locking pattern使用同步块加锁的方法会有两次检查instance == null一次在同步块外一次在同步块内因为会有多个线程一起进入同步块外的if中如果不在同步块内不进行二次检验就会导致生成多个实例
  • 单例模式双检锁Singleton示例
  • volatile:对于计算机中的指令而言,CPU和编译器为了提升程序的执行效率,通常会按照一定的规则对指令进行优化如果两条指令互不依赖,那么指令执行的顺序可能不是源码的编写顺序形如instance = new Instance()方法创建实例执行分为三步:分配对象内存空间:给新创建的Instance对象分配内存初始化对象:调用单例类的构造函数来初始化成员变量设置instance指向新创建的对象分配的内存地址,此时instance != null因为上面的初始化对象和设置instance指向新创建的对象分配的内存地址不存在数据上的依赖关系,无论哪一步先执行都不会影响最终结果,所以程序在编译时,顺序就会发生改变:分配对象内存空间设置instance指向新创建对象分配的内存地址初始化对象CPU和编译器在指令重排时,不会关心指令重排执行是否影响多线程的执行结果. 如果不加volatile关键字,如果有多个线程访问getInstance()方法时,如果刚好发生了指令重排,可能会出现以下情况:当第一个线程获取锁并且进入到第二个if方法后,先分配内存空间,然后instance指向刚刚分配的内存地址,此时instance不等于null.但是此时instance还没有初始化完成如果此时有另一个线程调用getInstance()方法,在第一个if的判断时结果就为false,就会直接返回没有初始化完成的instance,这样可能会导致程序NPE异常使用volatile的原因是禁止指令重新排序:volatile变量进行赋值操作后会有一个内存隔离读操作不会重排序到内存隔离之中比如在上面操作中,读操作必须在执行完1,2,3或者1,3,2步骤之后才会执行读取到结果,否则不会读取到相关结果

打开网易新闻 查看精彩图片

饿汉

  • 单例模式饿汉Singleton示例
  • 优点:在单例类中,装载类的时候就创建对象实例.因为单例类的实例声明为staticfinal变量,在第一次加在类到内存中时就会初始化,所以创建实例本身时线程安全的
  • 缺点:饿汉模式不是一种懒加载模式,即便客户端没有调用getInstance()方法,单例类也会在类第一次加载时初始化使用饿汉模式创建单例类实例在某些场景中无法使用:比如因为饿汉创建的实例声明为final变量如果单例类Singleton的实例的创建依赖参数或者配置文件需要在getInstance()方法之前调用方法为单例类的实例设置参数,此时这种饿汉模式就无法使用

静态内部类

  • 单例模式静态内部类Singleton示例
  • 使用静态内部类模式创建单例类实例是使用JVM机制保证线程安全:静态单例对象没有作为单例类的成员变量直接实例化,所以当类加载时不会实例化单例类第一次调用getInstance()方法时将加载静态内部类Nest.在静态内部类中定义了一个static类型的变量instance,这时会首先初始化这个变量通过JVM来保证线程安全,确保该成员变量只初始化一次由于getInstance()方法并没有加线程锁,所以对性能没有什么影响
  • 静态内部类的优点:静态内部类Nest是私有的,只能通过getInstance()方法进行访问,所以这是懒加载的读取实例时不会进行同步锁的获取,性能较好静态内部类不依赖JDK版本

枚举

  • 单例模式枚举Singleton示例
  • 使用枚举方式实现单例的最大特点是非常简单
  • 可以通过Enum.INSTANCE来访问实例,和getInstance()方法比较更加简单
  • 枚举的创建默认就是线程安全的方法,而且能防止反射以及反序列化导致重新创建新的对象Enum类内部使用Enum类型判定防止通过反射创建新的对象Enum类通过对象的类型和枚举名称将对象进行序列化,然后通过valueOf()方法匹配枚举名称找到内存中的唯一对象实例,这样可以防止反序列化时创建新的对象

打开网易新闻 查看精彩图片