java单例模式应用非常广泛,主要目的都是确保一个对象实例在整个应用中只会创建一次。实现单例的方式有以下几种,他们主要的区别在于是否线程安全,是否实现延迟加载,是否影响程序性能等等。
非线程安全的懒汉模式
public class LazySingleton { private static LazySingleton instance; private LazySingleton(){ } public static LazySingleton getInstance(){ if(null == instance){ //--------------------(1) instance = new LazySingleton(); } return instance; } }
之所以称之为懒汉模式,是因为这种模式有明显的延迟加载,只有在第一次调用getInstance方法时,才会实例化单例对象。在多线程场景下,若两个线程同时到达(1)代码处,此时实例为空,就会创建了两个LazySingleton对象,(实际上先创建的对象会被后创建的对象覆盖)。
线程安全的懒汉模式
public class LazySingletonSafe { private static LazySingletonSafe instance; private LazySingletonSafe(){ } public static synchronized LazySingletonSafe getInstance(){ if(null == instance){ instance = new LazySingletonSafe(); } return instance; } }
与非线程安全懒汉模式相比,线程安全模式通过synchronized关键字实现竞争锁达到线程安全的目的。这种模式每次都要去获取锁,操作比较重,影响性能,不推荐。
饿汉模式
public class HungrySingleton { private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return instance; } }
饿汉模式通过static静态变量内存常驻的特点实现单例,缺点是在类加载时即实例化单例对象,没有实现延迟加载,可能会导致占用系统资源导致资源浪费。
内部类单例模式
public class InnerSingleton { private static class InnerSingletonHolder{ private static InnerSingleton instance = new InnerSingleton(); } private InnerSingleton(){} public static InnerSingleton getInstance(){ return InnerSingletonHolder.instance; } }
静态内部类实现单例模式的好处是,不会在加载时就实例化单例对象,而是在调用getInstance方法时才实例化单例对象。由于静态内部类只有被调用时才会加载,所以这种方式实现了延迟加载,同时保证线程安全。
双重校验锁单例模式
public class DoubleCheckSingleton { private static DoubleCheckSingleton instance; private DoubleCheckSingleton(){} public static DoubleCheckSingleton getInstance(){ //只有实例为null时,才去竞争锁 if(null == instance){ synchronized (DoubleCheckSingleton.class){ //获得锁之后,第二次校验实例是否已被其他先获得锁的线程创建了 if(null == instance){ instance = new DoubleCheckSingleton(); } } } return instance; } }
双重校验锁单例模式是对懒汉模式的改良,只有单例还未实例化时才去竞争锁,而不用每次都要竞争锁。理论上这种实现方式是线程安全的,因为java编译器会把字节码命令重排序,在java虚拟机内部,完全有可能先new出来一个空的未调用过构造函数的instance对象,然后再将其赋值给instance引用,然后再调用构造函数,对instance对象当中的元素进行初始化。这样,就很有可能,当instance被赋值一个空的实例对象的时候,另一个线程调用了getInstance()这个函数,另一个线程发现,instance并不是空的,于是愉快地return回了那个空的instance对象。这样,一个空的instance对象的引用就被流传到了其他线程当中,为非作歹。