单例模式

1.单例模式的基本概念

整个程序全局中只能有一个属于该对象的类,并且只提供单一入口供使用者调用该类。也就是说,使用了单例模式的对象不允许在程序中出现两个相同的对象。

特点:

  • 单例模式全局只能有唯一实例
  • 单例模式全局的唯一实例必须由其自己创建
  • 单例模式必须提供其他调用方获取该实例的方法

2.单例模式的适用场景

生命周期短的对象

频繁访问io资源的对象,如数据库连接池,redis配置等

只需要调用该对象方法,对该对象没有任何修改动作且频繁使用的对象

3.单例模式示例

构建单例模式,一般需要三个语句

  • 初始化静态私有成员变量
  • 无参构造器
  • 对外暴露能够得到这个对象的方法

3.1 饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HungrySingleton {

// 1. 初始化静态私有成员变量
private static HungrySingleton hungrySingleton = new HungrySingleton();

// 2. 无参构造器
HungrySingleton(){}

// 3. 对外暴露能够得到这个对象的方法
public static HungrySingleton getHungrySingleton() {
return hungrySingleton;
}
}

优点:程序执行的时候就立刻初始化成员变量,在类被加载的时候就将初始化好的成员变量存放入内存中,在调用对象时运行速度会变快。

缺点:大量使用饿汉模式会占据一定的系统内存,导致系统运行速度变慢。

3.2 懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 静态私有唯一对象
private static LazySingleton lazySingleton;

// 2. 空参构造器
LazySingleton(){}

// 3. 静态方法获取该类对象暴露(需要加锁,防止多个对象在单例模式中的创建)
public static LazySingleton getLazySingleton() {
synchronized (LazySingleton.class) {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}

优点:等到需要调用该类对象的时候才初始化对象,一定程度上节约了内存

缺点:因为防止重复创建对象加上的锁导致多人调用的时候可能运行时间会较长,接近于串行运行。

3.3 双检锁模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 静态私有唯一对象
// 这里加volatile的原因是为了防止指令重排序而引发的返回一个没有初始化完全的单例对象
private static volatile DoubleCheckSingleton doubleCheckSingleton;

// 2. 空参构造器
DoubleCheckSingleton() {
}

// 3. 静态方法获取该类对象暴露(需要加锁,防止多个对象在单例模式中的创建)
// 再加一层判断,需要创建新对象的时候才用到锁
public static DoubleCheckSingleton getDoubleCheckSingleton() {
if (doubleCheckSingleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (doubleCheckSingleton == null) {
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
  1. volatile关键字:可以保证可见性和防止指令重排序,预防极端情况下因为指令重排序而引发的返回一个没有初始化完全的单例对象。

  2. 双检锁两层判断的意义:

    1. 第一层,判断是否已经获得了单例对象,如果获得了,没必要增强,直接使用,为了提高效率
    2. 第二层,锁内判断,防止获取两个单例对象,为了保证单例对象的唯一性

3.4 静态内部类模式

1
2
3
4
5
6
7
8
9
10
11
public class StaticClassSingleton {
private static class innerStaticClassHolder {
private static final StaticClassSingleton STATIC_CLASS_SINGLETON = new StaticClassSingleton();
}

StaticClassSingleton(){};

public static StaticClassSingleton getStaticClassSingleton() {
return innerStaticClassHolder.STATIC_CLASS_SINGLETON;
}
}

静态内部类单例模式:基本和双检锁差不多,都是调用后才创建对象,也能保证对象的唯一性

但是只能运用在静态方法类中 可保证线程安全(没有任何new的操作),也能保证单例的唯一性,同时也延迟了单例的实例化。

3.5 枚举模式

1
2
3
4
5
6
7
8
9
10
11
/**
* 枚举单例模式:防止反序列化造成的单例破坏
*/
public enum EnumSingleton {
ENUM_SINGLETON;

public static EnumSingleton getEnumSingleton() {
return ENUM_SINGLETON;
}

}

防止反序列化造成的单例破坏

推荐使用静态内部类模式,因为他不用new对象,不会产生多个对象的问题;

推荐使用枚举模式,因为他可以防止反序列化造成的单例破坏