探究单例模式(java)

探究单例模式(java)

单例模式的作用就是保证在整个系统中,某一个类的实例只有一个。单例模式可以避免产生多个对象而消耗过多的资源。比如访问数据库资源的时候,可以使用单例模式。

创建单例模式,要满足两个条件:

  • 构造函数必须是private。
  • 通过静态方法可以获取到单例类对象。

一般有以下几种方法来创建。

一、饿汉模式

 public class Singleton {
      private static final Singleton instance = new Singleton();
      // 构造函数私有
      private Singleton() {}

      public static Singleton getInstance() {
        return instance;
      }
    }

饿汉模式在声明静态对象时就已经实例化了。

二、懒汉模式

public class Singleton {
      private Singleton() {}
      private static Singleton instance;

      public static synchronized Singleton getInstance() {
        if (instance == null) {
          instance = new Singleton();
        }
        return instance;
      }
    } 

懒汉模式只有在使用时会实例化,在一定程度上节约了资源。但是第一次加载时需要及时进行实例化,反应慢。而且当多个线程调用getInstance()方法时,由于该方法是同步方法,因此只有一个线程能够获得锁,其他线程必须等待,造成了不必要的同步开销。

三、Double CheckLock

 public class Singleton {
      private static Singleton instance;
      private Singleton() {}

      public static Singleton getInstance() {
        if (instance == null) {
          synchronized (Singleton.class) {
            if (instance == null) {
              instance = new Singleton();
            }
          }
        }
        return instance;
      }
    }

Double CheckLock进行了两层判空操作。第一层是为了避免不必要的同步。当instance不为空时,可以不需要同步,直接拿到instance对象。第二层判空是为了在instance为null时,加入了同步锁,保证instance只实例化一次。

DCL没有了懒汉模式的同步开销。但是仍然有个小缺陷。

java在执行instance = new Singleton();时,将这段代码编译成了多条汇编指令:

  • 给Singleton实例分配内存空间。
  • 调用Singleton()构造函数,初始化成员字段。
  • 将instance对象指向分配的内存空间。

问题是后两条语句不是按顺序执行的,有可能先执行第二条,也有可能先执行第三条。当先执行第三条时,instance就不为空了,这时如果一个线程取走instance进行使用,由于没有初始化成员字段,导致程序出错。

为了避免这个问题,可以使用volatile关键字,它保证instance每次都从主内存读取。改进后的代码如下:

public class Singleton {
      // 加入volatile关键字
      private volatile static Singleton instance;
      private Singleton() {}

      public static Singleton getInstance() {
        if (instance == null) {
          synchronized (Singleton.class) {
            if (instance == null) {
              instance = new Singleton();
            }
          }
        }
        return instance;
      }
    }

四、静态内部类的方式

在网上看到一种创建单例模式的方法:

public class Singleton {
      private Singleton() {}
      public static Singleton getInstance() {
        return SingletonHolder.instance;
      }

      // 静态内部类里实例化Singleton
      private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
      }
    }

这种方法借鉴了饿汉模式,静态内部类里其实就做了饿汉模式的工作。这样,只有当调用getInstance()方法时,才会实例化Singleton。不仅线程安全,也能够保证单例对象的唯一。

总结

虽然单例模式是最容易理解的一种设计模式,但是其实现方式多样,而且各有优缺点,使用时特别注意。一般而言,Double CheckLock就可以满足程序要求。如果高并发情况下,可以引入volatile关键字。当然,静态内部类方式也是很好的一种解决方案。

你可能感兴趣的:(Java)