一. 前言

1. 定义

Singleton:确保某个类只有一个实例,并且自行实例化并向整个系统提供这个实例。

二. 实现方式

1. 饿汉式

1
2
3
4
5
6
7
8
9
10
public class Singleton {
//1.私有化构造方法
private Singleton(){}

//2.实例化对象,并提供获取方法
private static final Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}

优点:写法简单,线程安全。

缺点:如果没有使用过的话会造成内存浪费,没有懒加载的效果。

2. 懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Sinleton {
//1.私有化构造方法
private Singleton(){}

//2.声明对象
private static Singleton instance = null;

//3.第一次使用的时候才实例化对象
public static Singleton getInstacne(){
if (instance == null) {
instance = new Singleton();
}

return instance;
}
}

优点:实现了懒加载的效果。

缺点:线程不安全。

3. 懒汉式(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Sinleton {
//1.私有化构造方法
private Singleton(){}

//2.声明对象
private static Singleton instance = null;

//3.第一次使用的时候才实例化对象
public static synchronized Singleton getInstacne(){
if (instance == null) {
instance = new Singleton();
}

return instance;
}
}

优点:实现了懒加载的效果,线程安全。

缺点:使用 synchronized 会造成不必要的开销,而且大部分的时候我们是用不到同步的。

4. 双重锁定(DCL)

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
public class Sinleton {
//volatile 能够防止代码的重排序,保证得到的对象都是初始化过的
private volatile static Singleton instance;

//1.私有化构造方法
private Singleton(){}

//2.声明对象
private static Singleton instance = null;

//3.第一次使用的时候才实例化对象
public static Singleton getInstacne(){
//第一次检查,避免不必要的同步
if (instance == null) {
//同步
synchronized (instacne.class) {
//第二次检查
if (instacne == null) {
instance = new Singleton();
}
}
}

return instance;
}
}

优点:懒加载,线程安全,效率较高。

缺点:volatile影响一点性能,高并发下有一定的缺陷,某些情况下DCL会失效,虽然概率较小。

5. 静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Sinleton {
//1.私有化构造方法
private Singleton(){}

//2.从静态类中获取实例
public static Singleton getInstacne(){
return SingletonHolder.instance;
}

//3.静态类
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}

优点:懒加载,线程安全,推荐使用。

6. 枚举单例

1
2
3
4
public enum Sinleton {
//1.定义一个枚举元素,它就是实例
INSTNCE;
}

优点:线程安全,写法简单,能防止反序列化重新创建新的对象。

缺点:可读性不高,枚举会比静态常量多那么一丁点的内存。

7. 使用集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Sinleton {
//1.构建集合
private static Map<String,Object> map = new HashMap<>;

//2.注册
private static void registerInstance(String key, Object instance){
if (!map.contain(key)) {
//添加单例
map.put(key,instance);
}
}

//3.第一次使用的时候才实例化对象
public static Singleton getInstacne(String key){
return map.get(key);
}
}

优点:方便管理。

缺点:写法复杂。

三. 注意事项

1. 使用反射能够破坏单例模式,所以应该慎用反射

1
2
3
4
5
6
7
Constructor con = Singleton.class.getDeclaredConstructor();
con.setAccessible(true);
// 通过反射获取实例
Singleton singeton1 = (Singleton) con.newInstance();
Singleton singeton2 = (Singleton) con.newInstance();
System.out.println(singeton1==singeton2);
//结果为false,singeton1和singeton2将是两个不同的实例

可以通过当第二次调用构造函数时抛出异常来防止反射破坏单例,以懒汉式为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
private static boolean flag = true;
private static Singleton single = null;

private Singleton() {
if (flag) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被破坏!");
}
}

public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

2. 反序列化时也会破坏单例模式

可以通过重写readResolve方法避免,以饿汉式为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton implements Serializable {
private Singleton() {}

private static final Singleton single = new Singleton();

public static Singleton getInstance() {
return single;
}

//重写readResolve()
private Object readResolve() throws ObjectStreamException {
//直接返回单例对象
return single;
}
}

四. 优缺点以及应用场景

1. 优点

  • 内存中只存在一个对象,节省了系统资源。

  • 避免对资源的多重占用,例如一个文件操作,由于只有一个实例存在内存中,避免对同一资源文件的同时操作。

2. 缺点

  • 获取对象时不能用new。

  • 单例对象如果持有Context,那么很容易引发内存泄露。

  • 单例模式一般没有接口,扩展很困难,若要扩展,只能修改代码来实现。

3. 应用场景

  • 频繁访问数据库或文件的对象。
  • 工具类对象。
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象。

参考文章

Android设计模式-单例模式