饿汉式

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

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

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

懒汉式

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

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

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

解决方案 一

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

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

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

解决方案 二

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

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

使用双重判断加锁,首先进入该方法时进行null == sInstance检查,如果第一次检查通过,即没有实例创建,则进入synchronized控制的同步块,并再次检查实例是否创建,如果仍未创建,则创建该实例。
双重检查加锁保证了多线程下只创建一个实例,并且加锁代码块只在实例创建的之前进行同步。如果实例已经创建后,进入该方法,则不会执行到同步块的代码。

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

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

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

    本文作者: 赵俊 本文链接: http://www.zhaojun.im/Java基础-单例设计模式/ 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!