常用设计模式---单例模式

单例模式简单介绍

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类成为单例类,它提供全局访问的方法。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

单例模式需要注意事项

1.单例类的构造函数私有
2.提供一个自身的静态私有成员变量
3.提供一个公有的静态工厂方法

模式结构图

Alt

单例模式实例

这里模拟实现一个居民身份证唯一的单例场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 单例类如下
public class IdCardNo {

private static IdCardNo instance = null;
private String no;

private IdCardNo() {
}

public static IdCardNo getInstance() {
if (instance == null) {
instance = new IdCardNo();
instance.setNo("No5000011113333");
}
return instance;
}

public String getNo() {
return no;
}

private void setNo(String no) {
this.no = no;
}

单例的多种写法

上述场景中使用的是懒汉式写法,单例模式还有如下的几种写法
饿汉式:

1
2
3
4
5
6
7
8
9
10
11
public class EagerSingleton {  

private static EagerSingleton instance = new EagerSingleton();

private EagerSingleton() {
}

public static EagerSingleton getInstance() {
return instance;
}
}

饿汉式的写法可以保证线程安全,但从资源利用率角度来考虑,比懒汉式写法稍差。但懒汉式存在线程安全问题,所以接下来考虑多个线程同时首次引用单例的访问限制问题。
双重检测的单例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {  
//volatile禁止指令重排序,保证可见性
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;
}
}

双重检测虽然解决了多线程的访问限制问题,但这个写法看起来着实不美观。那么我们还有没有别的写法呢?答案是有的。
基于枚举的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
public enum SingletonEnum {  
INSTANCE;
private Singleton instance = null;

private SingletonEnum() {
instance = new Singleton();
}

public Singleton getInstance() {
return instance;
}

}

单元素的枚举类可以保证单例的线程安全、序列化,除单元素枚举外,还有使用 java 内部类实现的方式。
基于内部类实现单例:

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
public class Singleton implements Serializable {  

private Singleton() {
}

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

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

private Object readResolve(){
System.out.println("read resolve");
return SingletonHandler.instance;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton instance = Singleton.getInstance();

File file = new File("ser.out");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(instance);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton oIstance = (Singleton)ois.readObject();
ois.close();
System.out.println(oIstance == instance);
}
}

使用静态内部类的优点是:

外部类加载时并不需要立即加载内部类,即当 Singletonle 被加载时,并不需要去加载 SingletonHandler,只有当 getInstance() 方法第一次被调用时,才会去初始化 SingletonHandler,同时初始化该类的静态变量 instance ,在确保线程安全的同时也延迟了单例的实例化.

总结

一个类模板,在整个系统中只允许产生一个实例叫做单例。单例有多种写法:懒汉式、饿汉式、双重检查、枚举、内部类。

  • 饿汉式不管用不用先创建出来,保证线程安全。
  • 懒汉式延迟加载,有效利用资源不保证线程安全。
  • 双重检测方式保证了懒汉式的线程安全问题。
  • 单元素枚举可以同时保证线程安全和序列化。
  • 内部类使用了 jvm 的类加载机制来保证线程安全和懒加载。
  • 序列化和反序列化保证单例需要重写类的 readResolve() 方法
0%