Java基础:单例设计模式

饿汉式

1
2
3
4
5
6
7
public class Single {
private static Single s = new Single();
private Single() {}
public static Single getInstance () {
return s;
}
}

特点:Single 类一进内存,就已经创建好了对象。

存在的问题:

  • 如果构造方法中存在过多的处理,会导致加载这个类时比较慢,可能引起性能问题。
  • 如果使用饿汉式的话,只进行了类的装载,并没有实质的调用,会造成资源的浪费

懒汉式

1
2
3
4
5
6
7
8
9
10
public class Single {
private static Single s = null;
private Single() {}
public static Single getInstance () {
if (s == null) {
s == new Single();
}
return s;
}
}

特点:Single 类进内存,对象还没有存在,只有调用了 getInstance() 方法时,才建立对象。

存在的问题: 在多线程的情况下可能会出现安全问题:上述代码在多个线程并发调用 getInstance() 时,可能会创建出多个实例。比如 A 线程进行判断 s == null 这段代码后,还未创建实例之前,B线程也进入了 s == null 此代码块,那么就会造成创建了两个不同的实例的结果,违背了单例设计模式。

解决方案 一

只需要在 getInstance() 方法上加上 synchronized 修饰符,即可加锁,当一个线程进入此代码块后,其他线程无法进入。

1
2
3
4
5
6
7
8
9
10
public class Single {
private static Single s = null;
private Single() {}
public static synchronized Single getInstance () {
if (s == null) {
s == new Single();
}
return s;
}
}

但是这种解决办法,每个线程调用这个方法时,都要判断一下锁,效率会很低。

解决方案 二

为了解决方案一的效率低下的问题,可以用双重判断的形式来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Single {
private static Single s = null;
private Single() {}
public static Single getInstance () {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s == new Single();
}
}
}
return s;
}
}

使用双重判断加锁,首先进入该方法时进行null == sInstance检查,如果第一次检查通过,即没有实例创建,则进入synchronized控制的同步块,并再次检查实例是否创建,如果仍未创建,则创建该实例。

双重检查加锁保证了多线程下只创建一个实例,并且加锁代码块只在实例创建的之前进行同步。如果实例已经创建后,进入该方法,则不会执行到同步块的代码。

单例设计模式真的只有一个对象么

其实,单例模式并不能保证实例的唯一性,只要我们想办法的话,还是可以打破这种唯一性的。以下几种方法都能实现。

  • 使用反射,虽然构造器为非公开,但是在反射面前就不起作用了。
  • 如果单例的类实现了 cloneable,那么还是可以拷贝出多个实例的。
  • Java 中的对象序列化也有可能导致创建多个实例。避免使用 readObject 方法。
  • 使用多个类加载器加载单例类,也会导致创建多个实例并存的问题。
坚持原创技术分享,您的支持将鼓励我继续创作!