Java 设计模式——单例模式

HYF Lv3

单例模式,也叫单子模式,是一种常用的软件设计模式。单例模式确保一个类只有一个实例,提供一个全局访问点以访问该实例,属于创建型模式的一种。

单例模式的运用场景

单例模式的主要特点包括:

  • 唯一实例: 单例模式确保一个类只有一个实例存在。当程序首次请求该类的实例时,它会创建一个新的实例。随后的请求将返回该已创建的实例。
  • 全局访问点: 单例模式提供一个全局的访问点,允许其他部分在应用程序中访问该唯一实例。这样可以方便地共享实例的状态和功能。

以下是一些常见的单例模式的运用场景:

  • 配置管理器: 在应用程序中,可能会有一些全局配置需要在整个应用程序中共享。使用单例模式可以创建一个配置管理器类,确保在应用程序的任何地方都可以访问和更新配置信息。
  • 日志记录器: 在应用程序中实现日志记录功能时,使用单例模式可以确保所有部分都共享同一个日志记录器实例,避免产生多个日志文件或混乱的日志输出。
  • 数据库连接池: 在需要频繁访问数据库的应用程序中,使用单例模式可以创建一个数据库连接池,以确保连接资源得到有效的管理和重用。
  • 线程池: 如果应用程序需要处理大量并发任务,可以使用单例模式创建一个线程池,以控制并发线程的数量,避免过多的线程创建和销毁开销。
  • 缓存管理器: 在需要使用缓存来提高性能的场景中,可以使用单例模式来管理缓存,确保缓存的一致性和有效性。
  • 打印机管理器: 在需要管理打印任务的应用程序中,使用单例模式可以创建一个打印机管理器,确保打印任务按顺序进行。

单例模式的实现

普通饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 普通饿汉式单例模式
*
* @author -侑枫
* @date 2023/8/23 20:31:44
*/
public class HungryStyleSingleInstance {
/**
* 类加载时即实例化对象
*/
private static final HungryStyleSingleInstance HUNGRY_STYLE_SINGLE_INSTANCE = new HungryStyleSingleInstance();

/**
* 私有化构造器,防止外部 new 对象
*/
private HungryStyleSingleInstance() {
}

/**
* 使用静态方法获取对象实例
*
* @return 单例类对象
*/
public static HungryStyleSingleInstance getInstance() {
return HUNGRY_STYLE_SINGLE_INSTANCE;
}
}
  • 优缺点:
    • 优点:
      • 类加载时就进行实例化,之后的操作效率会很高。
      • 实现简单,代码清晰。没有复杂的线程同步逻辑,易于理解和维护。
    • 缺点:
      • 由于类加载时就进行实例化,如果后续不对此类进行任何操作,就会导致内存的浪费。

普通懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 普通懒汉单例(线程不安全)
*
* @author -侑枫
* @date 2023/8/23 20:37:44
*/
public class LazyStyleSingleInstance {

private static LazyStyleSingleInstance instance;

/**
* 私有化构造器,防止外部 new 对象
*/
private LazyStyleSingleInstance() {
}

/**
* 使用静态方法获取对象实例
*
* @return 单例类对象
*/
public static LazyStyleSingleInstance getInstance() {
if (instance == null) {
instance = new LazyStyleSingleInstance();
}
return instance;
}
}
  • 优缺点:
    • 优点:
      • 实例只有在需要的时候才会被创建,避免了启动时的资源浪费。这对于大型对象或有较高初始化成本的对象来说很有用。
    • 缺点:
      • 尽管懒加载有其优势,但在多线程环境下,每次调用 getInstance 都需要进行线程安全的检查,这可能会降低性能。
      • 当多个线程同时进入到 if(instance == null) {...} 时,会创建多个对象

同步锁懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 同步锁懒汉单例(线程安全)
*
* @author -侑枫
* @date 2023/8/23 20:43:58
*/
public class SynchronizedLazyStyleSingleInstance {
private static SynchronizedLazyStyleSingleInstance instance;

/**
* 私有化构造器,防止外部 new 对象
*/
private SynchronizedLazyStyleSingleInstance() {
}

/**
* 使用静态方法获取对象实例
*
* @return 单例类对象
*/
public static synchronized SynchronizedLazyStyleSingleInstance getInstance() {
if (instance == null) {
instance = new SynchronizedLazyStyleSingleInstance();
}
return instance;
}
}
  • 优缺点:
    • 优点:
      • 通过在 getInstance 方法上使用 synchronized 关键字,确保了在任何时候只有一个线程能够访问该方法,从而避免了在多线程环境下创建多个实例的问题。
      • 与前述懒汉式实现方式一样,实例只有在需要的时候才会被创建,避免了启动时的资源浪费。
    • 缺点:
      • synchronized 关键字会导致线程竞争和阻塞,从而降低性能。
      • 每次调用 getInstance 都需要获得锁,即使只有第一次调用时也需要。
      • 这种实现方式仍然是延迟加载的,但由于同步,可能会导致访问该方法的其他线程等待较长时间。且在高并发情况下,同步锁可能成为性能瓶颈,导致程序效率低下。导致性能问题。

双检索饿汉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 双检锁饿汉式单例模式
*
* @author -侑枫
* @date 2023/8/23 20:48:36
*/
public class DoubleCheckLockLazyStyleSingleInstance {
/**
* volatile是为了防止指令重排序
*/
private static volatile DoubleCheckLockLazyStyleSingleInstance instance;

/**
* 私有化构造器,防止外部 new 对象
*/
private DoubleCheckLockLazyStyleSingleInstance() {
}

/**
* 使用静态方法获取对象实例
*
* @return 单例类对象
*/
public static DoubleCheckLockLazyStyleSingleInstance getInstance() {
if (instance == null) {
synchronized (DoubleCheckLockLazyStyleSingleInstance.class) {
if (instance == null) {
instance = new DoubleCheckLockLazyStyleSingleInstance();
}
}
}
return instance;
}
}
  • 优缺点:
    • 优点:
      • 使用双重检查锁定和 volatile 关键字可以确保在多线程环境下创建唯一的实例,并且性能相对较好。这是因为只有在实例为 null 的情况下才会进入同步块,其他线程不会被阻塞。
      • 实例只有在需要的时候才会被创建,避免了启动时的资源浪费。
      • 与传统的同步方式相比,双重检查锁定方式提供了更高的并发度,减少了性能瓶颈。
      • volatile 关键字确保了在实例化对象时不会发生指令重排序,保证了实例的唯一性和正确性。
    • 缺点:
      • 双重检查锁定的实现相对复杂,需要开发人员充分理解并正确实现,以避免潜在的错误。
      • JDK 5 及以后的版本中,volatile 关键字的语义得到了改进,因此双检锁定须在 JDK 5 以后的版本才能确保安全
      • 需要在第一次创建实例时获得锁,这可能会导致性能稍有下降。

静态内部类懒汉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 静态内部类懒汉单例(线程安全)
*
* @author -侑枫
* @date 2023/8/23 20:52:19
*/
public class StaticInternalLazyStyleSingleInstance {
/**
* 静态内部类实例化对象
*/
private static class Singleton {
/**
* 类加载时进行实例化对象
*/
private static final StaticInternalLazyStyleSingleInstance STATIC_INTERNAL_LAZY_STYLE_SINGLE_INSTANCE =
new StaticInternalLazyStyleSingleInstance();
}

/**
* 私有构造,防止外部new对象
*/
private StaticInternalLazyStyleSingleInstance() {
}

/**
* 通过静态方法获取对象实例
*
* @return
*/
public static StaticInternalLazyStyleSingleInstance getInstance() {
return Singleton.STATIC_INTERNAL_LAZY_STYLE_SINGLE_INSTANCE;
}
}
  • 优缺点:
    • 优点:
      • 利用类加载机制和类初始化锁定,保证了在首次访问 getInstance 方法时才会加载内部类,从而实现了懒加载并保证了线程安全性。
      • 实例只有在需要的时候才会被创建,避免了启动时的资源浪费。
      • 静态内部类的方式在获取实例时没有显式的锁操作,因此在性能上较为高效。
      • 实现相对简洁,不需要开发人员显式地处理线程安全性,由 JVM 来保证。
      • 由于类加载机制的特性,静态内部类的实例在加载过程中不会发生指令重排序,保证了实例的唯一性和正确性。
    • 缺点:
      • 与所有懒汉单例一样,不适合需要提前初始化的场景(数据库连接池、配置管理器、全局缓存等)
1
2
3
4
5
6
7
8
9
10
/**
* 枚举类饿汉单例(线程安全且防止反序列化)
*
* @author -侑枫
* @date 2023/8/23 20:59:37
*/
public enum EnumSingleInstance {
// 实例
INSTANCE;
}
  • 优缺点:
    • 优点:
      • 枚举类的实例在加载时就被创建,由于枚举类的加载是由 JVM 管理的,所以在多线程环境下也可以保证唯一实例。枚举类的加载是延迟加载的,在首次被访问时才会被创建。因此,这是一种绝对线程安全的懒加载(懒汉单例)单例模式实现方式。
      • 枚举类天然地防止了反序列化攻击,因为枚举类的实例不能通过反序列化创建多个实例。
      • 枚举单例是一种非常简洁明了的实现方式,不需要开发人员处理线程安全性和懒加载逻辑。
      • 枚举类可以实现 Serializable 接口,从而实现了可序列化,可以在分布式系统中使用。
    • 缺点:
      • 枚举单例是一种最终类,无法被继承,因此无法扩展或添加额外的行为。
      • 枚举是在 JDK 5 版本引入的新内容,所以需在 JDK 5 及之后版本使用。

单例模式的优缺点

优点:

  • 单例模式提供了一个全局访问点,可以方便地访问唯一的实例,避免了需要频繁创建和传递实例的麻烦。
  • 单例模式可以避免重复创建相同类型的实例,节省了系统资源和内存消耗。
  • 懒汉式单例模式允许实例的延迟加载,只有在需要时才会创建,从而提高了系统的性能和启动速度。
  • 线程安全的单例模式所提供的全局访问点,避免了多线程环境下的竞态条件和数据不一致问题。

缺点:

  • 单例模式一般来说是静态的,不容易扩展为多个实例。如果在未来需要支持多个实例,可能需要修改现有的代码。
  • 单例模式可能会导致一些高耦合的设计,因为它在整个系统中引入了全局状态。这可能使得代码的可测试性和维护性降低。
  • 单例模式的实例既负责自己的职责,又充当了全局访问点,可能会导致一个类承担过多的职责,即违反单一职责原则(一个类应该只实现一个逻辑,而不用关心它是否是单例的。)。
  • 单例模式可能会导致代码难以测试,因为全局状态的存在可能会影响测试用例之间的互相干扰。
  • 单例模式隐藏了实例的创建和管理细节,可能使得代码可读性变差。

最后附上本文所写源代码:设计模式——单例模式

  • 标题: Java 设计模式——单例模式
  • 作者: HYF
  • 创建于 : 2023-08-23 22:26:32
  • 更新于 : 2024-07-27 21:21:50
  • 链接: https://youfeng.ink/singleInstance-bfd1de64f3a4/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。