设计模式
🗒️单例设计模式
00 min
2022-9-16
2023-7-31
type
status
date
slug
summary
tags
category
icon
password

一、概念

确保一个类只有一个实例,并提供该实例的全局访问点。以一个私有构造函数一个私有静态变量一个公有静态函数来实现。单例模式是一种对象创建型模式,主要关键点是某个类只能有一个实例、这个类必须自己创建这个实例、必须向整个系统提供这个实例。
  • 优点:提供了唯一实例的受控访问,节约系统资源(对于一些需要频繁创建和销毁的对象)
  • 缺点:没有接口,不能继承。一定程度上违背了单一职责原则(单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,可能会包含一些业务代码)
  • 使用场景:要求生产唯一序列号,WEB中的计数器(不用每次刷新都在数据库中加一次,用单例先缓存),创建一个对象需要消耗的资源过多(如I/O与数据库的连接等)
  • 注意getInstance()需要使用同步锁synchronized(Singletion.class)防止多线程同时进入造成instance被多次实例化。
notion image

二、饿汉式(推荐)

天生的线程安全单例模式,类加载的时候就已经进行实例化了,在getInstance()方法之前实例就存在了,多线程情况下是安全的。但容易产生垃圾对象。
基于 classloader机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
  • 优点:线程安全,没有加锁,执行效率会提高。
  • 缺点:实例在类加载的时候就初始化,可能存在浪费内存的情况

三、懒汉式(线程不安全_不推荐)

懒加载模式,最基本的实现方式,最大的问题是不支持多线程的情况。由于getInstance()没有加锁,在多线程的情况下可能会同时调用该方法,创建多个实例。

四、懒汉式(线程安全,同步方法_不推荐)

解决了线程安全的问题,但是效率太低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而该方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行,方法进行同步效率太低

五、懒汉式(线程安全,同步代码块)

六、双检锁/双重校验锁(DCL,即 double-checked locking_推荐)

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance()的性能对应用程序很关键。实例化代码只执行了一次,后面再次访问时,判断if(instance==null),直接return实例化对象,也避免的反复进行方法同步。

七、静态内部类

可以达到和双检锁方式一样的功效,实现更简单。此方式是只适用于对静态域使用延迟初始化,双检锁可在实例语需要延迟初始化时使用。
这种方式同样利用了classLoader机制来保证初始化instance时只有一个线程。该方式是将instance的初始化方式放在静态内部类中,类加载的时候,静态内部类会被装载了,但是instance并不一定被初始化。只有通过显式调用 getInstance()方法时,才会显式装载 SingleToInstance类,从而实例化 instance
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

八、枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 《Effective Java》作者(Josh Bloch)提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。
上一篇
数据结构基础
下一篇
工厂模式