IOTXING

记录技术学习之路

0%

java设计模式-单例模式

懒汉模式

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

private static volatile SingletonDemo instance;

private String value;

public static SingletonDemo getInstance() {
if (instance == null) {
instance = createInstance();
}
return instance;
}

private static SingletonDemo createInstance() {
return new SingletonDemo();
}

private SingletonDemo() {
}
}
  • 使用private的构造函数,确保无法通过new的方式,来创建一个新的实例,在使用new的时候会提示。

https://iotxing-1253163150.cos.ap-shanghai.myqcloud.com/%E7%A7%81%E6%9C%89%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0.jpg

  • 通过volatile保证,在获取的时候,实例始终是最新的。
  • 在使用getInstance获取实例的时候,如果是第一次获取,会调用private的创建实例的方法,来进行实例的创建,然后赋值给static的变量。
  • 上面会存在一个问题,可能同时有多个线程进行实例的创建,例如10个线程同时执行获取实例方法,并且instance都为空,那么每个线程都会尝试去调用createInstance的方法来进行instance的创建,就会产生无效的性能损耗

为了验证这个情况,我们可以通过测试来验证下

1
2
3
4
5
private static SingletonDemo createInstance()
{
System.out.println("开始创建"); //我们在原有的创建实例的地方进行打印
return new SingletonDemo();
}

书写测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
for (int i = 0; i < 10; i++)
{
new Thread(() -> {
try
{
cyclicBarrier.await();
SingletonDemo singletonDemo = SingletonDemo.getInstance();
} catch (Exception e)
{
System.out.println("出现了异常");
}
}).start();
}

保证10个线程,同时开始进行实例的获取,然后看输出情况

https://iotxing-1253163150.cos.ap-shanghai.myqcloud.com/%E6%87%92%E6%B1%89%E5%8A%A0%E8%BD%BD.jpg

通过结果我们能发现,createInstance被调用了十次,而这十次里面有九次都是我们不希望出现的。

双重检查

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
public class SingletonDemo
{
private static volatile SingletonDemo instance;

public static SingletonDemo getInstance()
{
if (instance == null)
{
synchronized (SingletonDemo.class)
{
if (instance == null)
{
instance = createInstance();
}
}

}
return instance;
}

private static SingletonDemo createInstance()
{
System.out.println("开始创建");
return new SingletonDemo();
}

private SingletonDemo()
{
}
}

通过第一种方法的测试,我们能够发现,同时有多个线程进入到了createInstance的阶段,准备创建实例。现在我们需要对SingletonDemo类进行同步,确保同时只有一个线程能够拿到SingletonDemo的锁,然后进入到下面的代码。

为了保证在拿到锁的时候,实例还没有被创建出来,我们需要进行二次检查,避免一个线程创建完成之后,另外一个线程拿到锁,再次进入然后创建对象。

如果在检测到实例为null之后,会先尝试获取锁,如果拿到锁,并且实例还是为空,因为实例使用了volatile关键字,保证了时效性,所以这个时候一定是没有别的线程抢到锁并且开始创建对象,持有锁的线程可以放心的创建对象,然后返回。之后的线程如果拿到了锁,但是由于实例已经被创建出来了,因此也不会进入到创建实例的方法。

我们再次测试验证下,发现的确只打印了一次。

https://iotxing-1253163150.cos.ap-shanghai.myqcloud.com/%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5.jpg

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SingletonInnerDemo
{
private SingletonInnerDemo()
{
System.out.println("开始创建");
}

public static SingletonInnerDemo getInstance()
{
return inner.instance;
}

static class inner
{
private static final SingletonInnerDemo instance = new SingletonInnerDemo();
}
}
  • 通过使用内部类的方式,来限定访问的范围。设定了inner内的instance的访问范围为private,也就是无法在该类的外部进行访问。保证了无法通过直接调用的方式来进行新对象的创建。
  • 使用私有化构造函数,保证无法通过new的方式来进行创建
  • 因为是内部类,所以只有在第一次被引用的时候,才会进行初始化
  • 因为在初始化的时候,使用的是类加载进行实例的加载,能够保证线程的安全性。