单例模式

此处输入图片的描述

1、懒汉式

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

private Demo1() {
super();
}

private static Demo1 instance = null;

public static Demo1 getSingleton() {
if (null == instance) {
instance = new Demo1();
}
return instance;
}
}

2、饿汉式

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

private Demo2() {
super();
}

private static Demo2 instance = new Demo2();

public static Demo2 getSingleton() {

return instance;
}

}

为什么这样写呢?我们来解释几个关键点:

  1. 要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Demo1的构造方法是私有的
  2. instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。
  3. getInstance是获取单例对象的方法。

如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。

如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。

3、双重验证

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

public Demo3() {
super();
}

private volatile static Demo3 instance = null; // volatile 禁止了指令重排

public Demo3 getSingleton() {
if (null == instance) {
synchronized (Demo3.class) { // 同步锁
if (null == instance) {
instance = new Demo3();
}
}
}
return instance;
}

}

为什么这样写呢?我们来解释几个关键点:

  1. 为了防止new Demo3被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。
  2. 进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。
  3. volatile 禁止了指令重排

4、静态内部内

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

private Demo4() {
super();
}

private static class LazyHolder {
private static final Demo4 INSTANCE = new Demo4();
}

public static Demo4 getSingleton() {
return LazyHolder.demo4;
}

}

这里有几个需要注意的点:

  1. 从外部无法访问静态内部类LazyHolder,只有当调用Demo4.getInstance方法的时候,才能得到单例对象INSTANCE。
  2. INSTANCE对象初始化的时机并不是在单例类Demo4被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。
  3. 可能被反射打破单例的约束

5、枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Demo5 {
demo5;

private Object obj = null;

private Demo5() {
obj = new Object();
}

public Object getSingleton() {
return obj;
}

}

使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。

对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。

坚持原创技术分享,您的支持将鼓励我继续创作!
0%