单例模式

一、概述

单例设计模式:一个类只允许创建一个对象或者实例(唯一的范围:进程),那这个类就是一个单例类,这种设计模式即为单例设计模式。

为什么需要单例模式 ?

1、用于标识全局唯一的类(配置类、连接池类、ID生成器) 2、解决资源访问冲突的问题

为什么不用静态方法而用单例模式?

两个都能实现我们加载的目的,静态方法基于对象的,单例模式是面向对象的。一个方法和所在的实例对象无关,那么它可以为静态方法。当方法与实例有关,但是在创建类时又只需要一份实例,则需要单例模式。(如电商中的类包含一些配置和属性并且是公共的,所以只需要一份就可以了)

二、实现

需要注意的点

  • 构造函数需要被 private 访问修饰符修饰,避免外部通过 new 关键字创建实例
  • 对象创建时的线程安全问题
  • 是否需要延时加载
  • 考虑 getInstance() 性能是否高(是否加锁)

2.1 饿汉式

instance 静态实例在类加载时已经创建并初始化好了

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
// 创建实例对象
private static Singleton instance = new Singleton();

// private修饰,构造器私有化
private Singleton() {
}

// 静态方法获取实例对象
public static Singleton getInstance() {
return instance;
}
}

2.2 懒汉式

相比于饿汉式,懒汉式的优势是支持延迟加载(当真正需要实例时,创建实例),懒汉式需要在方法上进行加锁(不加锁会出现并发问题),并发度为 1 ,如果频繁的用到这个单例,就会频繁加锁和释放锁等问题,这种方式则不可取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private static Singleton instance;

private Singleton() {
}


public static synchronized Singleton getInstance() {
// 判断实例对象是否创建
if (instance == null) {
// 未创建则创建对象
instance = new Singleton();
}
return instance;
}
}

2.3 双重检测

懒汉式的问题在于性能问题,不支持高并发问题。通过双重检测,只要 instance 被创建后,再调用 getInstance() 函数不会进入锁逻辑中,解决了懒汉式并发度低的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {

private static volatile Singleton instance;

private Singleton() {
}


public static Singleton getInstance() {
// 判断实例对象是否创建
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
// 未创建则创建对象
instance = new Singleton();
}
}
}
return instance;
}
}

对于实例变量需要使用 volatile 关键字修饰,原因:创建一个对象分为三步:1. 分配内存空间 2. 初始化内存空间 3. 将内存地址赋值给变量,由于指令重排可能顺序变为:1—> 3 —> 2 ,导致一个线程拿到未初始化的对象,所以需要加 volatile 禁止指令重排,并且可以保证多个线程间该对象的准确性。

2.4 静态内部类

利用 Java 的静态内部类,当外部类 IdGenerator 被加载时,并不会创建 Singleton 实例对象,只有当调用 getInstance() 方法时,IdGenerator 会被创建实例,实例创建过程的线程安全性,由 JVM 保证。

1
2
3
4
5
6
7
8
9
10
11
public class IdGenerator {
private IdGenerator() {}

private static class Singleton {
private static final IdGenerator instance = new IdGenerator();
}

public static IdGenerator getInstance() {
return Singleton.instance;
}
}

2.5 枚举

通过 Java 枚举本身的性质,保证了实例的线程安全性和实例的唯一性。

1
2
3
public enum Singleton {
INSTANCE;
}

三、存在的问题

  1. 对OOP(封装、抽象、继承、多态)特性不友好

单例模式违背了基于接口而非实现的设计原则(当需要两个独立的ID生成器时,就会产生较大的变动)

  1. 单例会隐藏类之间的依赖关系

  2. 对扩展不友好(当代码中需要多个实例时,代码改动较大)

  3. 单例不支持有参数的构造函数

要解决以上问题,可以通过工程模式、IOC容器、程序员自己保证来解决。


单例模式
https://pursuemilk.github.io/2023/02/24/单例模式/
作者
PursueMilk
发布于
2023年2月24日
许可协议