饿汉式
public class Single {
private static Single s = new Single();
private Single() {}
public static Single getInstance () {
return s;
}
}
特点:Single 类一进内存,就已经创建好了对象。 存在的问题:
- 如果构造方法中存在过多的处理,会导致加载这个类时比较慢,可能引起性能问题。
- 如果使用饿汉式的话,只进行了类的装载,并没有实质的调用,会造成资源的浪费
懒汉式
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 修饰符,即可加锁,当一个线程进入此代码块后,其他线程无法进入。
public class Single {
private static Single s = null;
private Single() {}
public static synchronized Single getInstance () {
if (s == null) {
s == new Single();
}
return s;
}
}
但是这种解决办法,每个线程调用这个方法时,都要判断一下锁,效率会很低。
解决方案 二
为了解决方案一的效率低下的问题,可以用双重判断的形式来实现:
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 方法。
- 使用多个类加载器加载单例类,也会导致创建多个实例并存的问题。
本文作者: 赵俊 本文链接: http://www.zhaojun.im/Java基础-单例设计模式/ 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!