单例模式是软件设计中非常常见的模式,但真正用好也用对的好像还有很多路要走。一起来研究一下。
单例,最最起码得有这些吧
- 私有的构造方案
- 一个 static 的实例
- 一个 static 的外部访问入口
具体代码应该是这样的(懒汉模式)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
|
当然了,看着好像是那么回事,完成了以上操作,我们来测试一下
1
2
3
4
5
6
7
8
9
10
|
public class SingletonTest {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
//test1
Singleton singleton = Singleton.getInstance();
System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
}
}
}
|
输出(每次运行输出是不一样的)
1
2
3
|
running at threadmain>>cn.zhucongqi.singleton.Singleton@49476842
running at threadmain>>cn.zhucongqi.singleton.Singleton@49476842
running at threadmain>>cn.zhucongqi.singleton.Singleton@49476842
|
单例完成了!?
我们在多线程下测试一下,改造一下
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
|
public class SingletonTest {
public static void main(String[] args) {
// for (int i = 0; i < 3; i++) {
// //test1
// Singleton singleton = Singleton.getInstance();
// System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
// }
//test2
Runnable r = new Runnable() {
@Override
public void run() {
Singleton singleton = Singleton.getInstance();
System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
}
};
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
|
输出(每次运行输出是不一样的)
1
2
3
4
5
6
|
running at threadThread-2>>cn.zhucongqi.singleton.Singleton@a1a1c96
running at threadThread-5>>cn.zhucongqi.singleton.Singleton@b0545bc
running at threadThread-4>>cn.zhucongqi.singleton.Singleton@a1a1c96
running at threadThread-3>>cn.zhucongqi.singleton.Singleton@a1a1c96
running at threadThread-0>>cn.zhucongqi.singleton.Singleton@b0545bc
running at threadThread-1>>cn.zhucongqi.singleton.Singleton@b0545bc
|
出现问题了!!!怎么在多线程下出现了不同实例了???是不是我们的设计有问题呢?我们在图示位置打个断点,按照 Thread 来处理
然后 Debug 一下
我们先来看看 Thread0 的情况
看到此时instance
为{Singleton@475}。我们换到 Thread1 再看看
看到此时instance
也是{Singleton@475}。往下走
额等等,怎么instance
变成{Singleton@476}
了,被覆盖咯???
看来我们的设计有问题哦!
怎么改造呢?
从 Debug 来看,是getInstance()
时出现了线程安全问题,我们用synchronized
是不是可以解决问题呢?我们再改造一下
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public synchronized static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
|
再 Debug 一下。
看到就一个 Thread 是 running 的,其他的是 Monitor 的,是不是可以了。我们往下继续走。
看到,Thread0 走出来之后,Thread4 才进入到 Running 状态。也就是加入synchronized
把整个class都锁住了。目的达到,但是性能不好。我们再改造一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (null == instance) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
|
再Debug 看看
现在所有 Thread 都是 Running 的了。我们继续走,先看看 Thread0
再看看 Thread3
看样子又要被覆盖咯!!!看来还是不行哦!是不是我们再加一把锁哦?!
我们再改造一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (null == instance) {
synchronized(Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
|
这次我们先 Run 一下看看结果,输出
1
2
3
4
5
|
running at threadThread-2>>cn.zhucongqi.singleton.Singleton@b0545bc
running at threadThread-0>>cn.zhucongqi.singleton.Singleton@b0545bc
running at threadThread-1>>cn.zhucongqi.singleton.Singleton@b0545bc
running at threadThread-3>>cn.zhucongqi.singleton.Singleton@b0545bc
running at threadThread-4>>cn.zhucongqi.singleton.Singleton@b0545bc
|
结果对咯!为什么呢?
其实,刚才的效果和不断的改造,我们把最初的Singleton
改造成了DoubleCheck 的懒汉式Singleton了。
当然了,上面这种方式只是一种方式罢了。下面直接上其他的方式Code。
2020.2.20更新
在Doublecheck的第一次check的时候,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。主要原因是重排序,**所以只需要做一点小的修改(把instance声明为volatile型),就可以实现线程安全的延迟初始化。**因为被volatile关键字修饰的变量是被禁止重排序的。
1
|
private static volatile Singleton instance = null;
|
EnumSingleton 枚举方式实现的单例
1
2
3
4
5
6
7
8
9
|
// Enum Singleton
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
|
InnerClassSingleton 内部类方式实现的单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Inner class Singleton
public class InnerSingleton {
private InnerSingleton(){
if (null != InnerHolder.INSTANCE) {
throw new RuntimeException("Just only one Instance can be create!");
}
}
//Serializable
private Object readResolve() {
return InnerHolder.INSTANCE;
}
public static InnerSingleton getInstance() {
return InnerHolder.INSTANCE;
}
private static class InnerHolder {
private static final InnerSingleton INSTANCE = new InnerSingleton();
}
}
|
特别说明
- 为了避免序列化出现的 Singleton 实例不唯一的情况,覆盖 Object 的readResolve方法。
1
2
3
|
private Object readResolve() {
return InnerHolder.INSTANCE;
}
|
- 为了避免,通过反射错误的使用 Singleton,在构造中做再处理
1
2
3
4
5
|
private InnerSingleton(){
if (null != InnerHolder.INSTANCE) {
throw new RuntimeException("Just only one Instance can be create!");
}
}
|