Radio

一个小小程序员

0%

单例模式

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例类只能有一个实例。

单例类必须自己创建自己的唯一实例。

单例类必须给所有其他对象提供这一实例。

单例模式的5种实现方式

懒汉式-线程不安全

懒得创建,你调用我的时候再创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package test.singleton;

import java.io.Serializable;

/**
* 懒汉式
*/
public class SingletonOne {

private static SingletonOne instance = null;

private SingletonOne() {
}

public static SingletonOne getInstance() {
if (instance == null) {
instance = new SingletonOne();
}
return instance;
}
}

饿汉式-天生线程安全

早就创建好了,随时等着调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package test.singleton;

/**
* 饿汉式
*/
public class SingletonTwo {
private static SingletonTwo instance = new SingletonTwo();

private SingletonTwo() {
}

public static SingletonTwo getInstance() {
return instance;
}
}
双检锁/双重校验锁(DCL,即 double-checked locking)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package test.singleton;

/**
* 双检锁/双重校验锁(DCL,即 double-checked locking)
*/
public class SingletonThree {
private volatile static SingletonThree instance = null;

private SingletonThree() {
}

public static SingletonThree getInstance() {
if (instance == null) {
//synchronize加在这儿的好处是,第一是为了线程安全,第二是如果已经被实例化了,那么就不用加锁,直接获取对象,相比把锁加在方法上效率要高一些。
synchronized (SingletonThree.class) {
if (instance == null) {
instance = new SingletonThree();
}
}
}
return instance;
}
}

需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值,此时就可以将分配的内存地址赋值给instance字段了。然而该对象可能还没有初始化(写完还未同步主存),若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

由于 synchronized 并不是对 instance实例进行加锁(因为现在还并没有实例),所以线程在执行完instance = new SingletonThree()修改 instance 的值后,应该将修改后的 instance立即写入主存(main memory),而不是暂时存在寄存器或者高速缓冲区(caches)中,以保证新的值对其它线程可见。

至此,我们要加volatile关键字,保证内存可见性。

登记式/静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package test.singleton;

/**
* 登记式/静态内部类
*/
public class SingletonFour {
private SingletonFour() {
}

private static class SingletonFourHolder {
private static final SingletonFour INSTANCE = new SingletonFour();
}

public static final SingletonFour getInstance() {
return SingletonFourHolder.INSTANCE;
}
}
枚举
1
2
3
4
5
6
7
8
9
10
11
package test.singleton;

/**
* 枚举
*/
public enum SingletonFive {
INSTANCE;
public void doSomeThing(){
System.out.println("123321");
}
}

使用:

1
2
//枚举调用方式
SingletonFive.INSTANCE.doSomeThing();
说明:

一般情况下,不建议使用第 1 种方式,建议使用第 2 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。

破坏单例模式的三种方式

反射,克隆,序列化

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
33
34
package test.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
* 破坏单例的两种方式
* 1,反射
* 2,克隆 需要单例实现Cloneable接口
* 3,序列化 需要单例实现Serializable接口
*/
public class SingletonDemo {
public static void main(String[] args) throws Exception {
System.out.println("---------------序列化------------------");
SingletonOne singleton = SingletonOne.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(singleton);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois =new ObjectInputStream(bis);
SingletonOne serializeSingleton = (SingletonOne) ois.readObject();
System.out.println(singleton == serializeSingleton);

System.out.println("---------------克隆------------------");
SingletonOne cloneSingleton = (SingletonOne) singleton.clone();
System.out.println(singleton == cloneSingleton);

System.out.println("---------------反射------------------");
Constructor<SingletonOne> cons = SingletonOne.class.getDeclaredConstructor();
cons.setAccessible(true);//SingletonOne类中的成员变量为private,故必须进行此操作
SingletonOne reflextSingleton = cons.newInstance();
System.out.println(singleton == reflextSingleton);
}
}

运行结果:

1
2
3
4
5
6
---------------序列化------------------
false
---------------克隆------------------
false
---------------反射------------------
false

这三种方式的规避方法

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
33
34
35
36
37
38
39
40
package test.singleton;

import java.io.Serializable;

/**
* 懒汉式
*/
public class SingletonOne implements Serializable, Cloneable {
//防止反射
private static volatile boolean isCreate = false;//默认是第一次创建

private static SingletonOne instance = null;

private SingletonOne() {
if (isCreate) {
throw new RuntimeException("已创建过");
}
isCreate = true;

}

public static SingletonOne getInstance() {
if (instance == null) {
instance = new SingletonOne();
}
return instance;
}

@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
//防止克隆
return instance;
}

//防止序列化
private Object readResolve() {
return instance;
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---------------序列化------------------
true
---------------克隆------------------
true
---------------反射------------------
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at test.singleton.SingletonDemo.main(SingletonDemo.java:31)
Caused by: java.lang.RuntimeException: 已创建过
at test.singleton.SingletonOne.<init>(SingletonOne.java:16)
... 5 more

这里我们简单说一下 为什么防止序列化要添加readResolve(),返回Object对象

一般来说, 一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而”组装”成一个跟原来一模一样的对象.

不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来,与以前序列化的对象不能equlas。

如果被反序列化的对象的类存在readResolve这个方法,他会调用这个方法来返回一个“array”,然后浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。

欢迎关注我的其它发布渠道