五、单例模式(Singleton Pattern)《HeadFirst设计模式》读书笔记
1.单例模式的定义:确保一个类只有一个实例,并提供一个全局访问点。
编写单例实例的要点是:
1)将构造方法私有化,确保别的类不能通过构造方法创建对象;
2)在本类中通过私有化的构造方法创建对象;
3)提供一个静态方法让其它类可以获取这个对象。
使用静态方法是因为普通方法要通过对象来调用,而其它类不能获取到对象,所以只能通过静态方法调用。
2.单例模式可以分为两种类型:饿汉式和懒汉式。
1)饿汉式
饿汉式非常饥渴,在类初始化时就已经将对象创建好了。
public class HungerSingleton { private static HungerSingleton singleton = new HungerSingleton(); private HungerSingleton(){ } public static HungerSingleton getInstance() { return singleton; } }
饿汉式同样可以用枚举来简洁的实现:
public enum SingletonEnum { INSTANCE }
在该枚举中只声明一个INSTANCE变量,它就是单例的,使用时可以通过枚举类名.变量名来调用。
2)懒汉式
懒汉式非常懒,只有在其它类调用了获取对象的方法时才创建对象,其实就是懒加载。
public class LazySingleton { private static LazySingleton singleton; private LazySingleton(){ } public static LazySingleton getInstance() { //判断Singleton对象是否已经创建,如果为null就先创建,不为null就直接返回 if (singleton == null) { singleton = new LazySingleton(); } return singleton; } }
3.懒汉式单例模式的问题和改进
在多线程的环境下,如果多个线程想要获取对象实例,可能会产生线程安全问题:如果线程A判断singleton==null后释放了CPU的使用权,由线程B再执行全部过程,则线程B创建好对象之后,线程A又会创建一个新的对象,这样就不是单例了。下面提供了几种解决方法:
1)对getInstance()方法加上synchronized关键字,这样虽然保证了线程安全,但是效率却大大降低,因为实际上只有第一次Singleton对象为null时才会产生线程安全问题(因为不为null时就直接返回了,不会执行if里面的创建对象代码);
2)双重检测加锁。在1)的基础上,在synchronized的外层再加一重Singleton对象是否为null的判断,用于第一次为null时对象的创建,同时将类变量声明成volatile,防止指令重排产生的问题。这样只有第一次为null的时候会执行synchronized代码块,后面单例对象已经创建好了会执行外层if里的代码而直接返回了。
public class DoubleCheckLazySingleton { private static volatile DoubleCheckLazySingleton singleton; private DoubleCheckLazySingleton(){ } public static DoubleCheckLazySingleton getInstance() { if (singleton == null) { synchronized(DoubleCheckLazySingleton.class){ if (singleton == null) { singleton = new DoubleCheckLazySingleton(); } } } return singleton; } }
3)静态内部类
在静态内部类中声明静态变量并创建对象,并在单例类中提供获取实例的方法,通过类名.变量名的方式获取到这个唯一的实例,因为只有在该方法被调用时静态内部类才会被加载并创建实例,因此这种方式是一种线程安全的懒汉式单例模式。
public class InnerStaticClassSingleton { private InnerStaticClassSingleton() { } //静态内部类 private static class InnerClass{ private static InnerStaticClassSingleton singleton = new InnerStaticClassSingleton(); } public static InnerStaticClassSingleton getInstance(){ return InnerClass.singleton; } }
4.总结
1)单例模式就是将构造方法私有化,再在类内部创建对象并提供获取该对象的静态方法;
2)单例模式分为懒汉式和饿汉式,懒汉式可能会产生线程安全问题,可以通过加锁或者双重检测或静态内部类来解决。如果并不需要频繁的获取对象,只需要简单的加锁就可以了,效率也不会低太多;
3)单例的类因为构造方法都是私有化的,因此不能被继承,如果需要继承只能通过修改构造方法的权限修饰符,这就不是严格的单例了;此外单例模式是利用静态变量实现的,继承就意味着所有子类都共享这一变量,因此涉及单例的继承时要谨慎考虑。