单例模式
1. 要求
确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
1.1 构造方法的要求
确保一个类只有一个实例,那么就要求它的构造方法一定不能是public公开的,即不能被外界进行实例化。那么他的构造方法只能是private。
1.2 实例的要求
接着,他只有一个实例,这个实例属于当前类,即这个实例是当前类的类成员变量,静态变量。
类变量其实就是静态变量,即用static修饰。通过上半部分可以看出这种设计模式要求构造方法是private,并且拥有一个当前类的静态成员变量。
后面他要求向整个系统提供这个实例,即我们要再提供一个静态的方法, 来向外界提供当前类的实例。当前实例只能在内部进行实例化,它不能够放到外面去。
2. 使用场景
单例模式的主要作用是确保一个类只有一个实例存在,比如说序列号生成器、Web页面的计数器等等都可以使用单例模式。同时如果创建某个对象需要消耗较多资源的话,比如访问IO、或者数据库资源的时候,我们也可以使用单例模式来减少资源的消耗。
3. 实例化的时间不同
对于一个实现了单例模式的类来说,它首先构造方法必须是私有的,即Private。接着,它拥有一个唯一的实例,即它的类变量,静态变量,用static修饰的。但是我们singleton在什么时候进行实例化。可以看到它刚开始是空的,根据实例化实际不同。
4. 单例模式的分类
其实单例模式可以分为两种,饿汉式和懒汉式。
饿汉式,即在类加载的时候就进行实例化。
懒汉式,即在类加载的时候不进行实例化,在第一次使用的时候再进行实例化。在这里如果singleton等于空的时候,将其进行实例化。这里需要加锁来防止它被多次实例化。
4.1 饿汉模式
还没等你去调用,它就先给你创建出来。就比如说先可以static等于,然后你创建的那个Instance对象直接给你声明出来了。
4.2 懒汉模式
可以用到内部类,内部类有一个好处,利用了类在加载的特性,只有类被利用到的时候,它才会被加载,而且只能加载一个。因为这个是根据ClassLoader来实现的。这是它们底层的东西了。
5. 为什么会出现多次实例化
两个线程同时执行完非空判断之后。因为两者开始均为空,所以两者都要进行实例化。如果不做同步的话,则需要实例化两次。所以我们加锁,来保证singleton只会被实例化一次。
6. 双重检查锁
懒汉式实现模式中非常经典的实现模式。
首先,我们可以看到在做同步处理的范围是整个方法,但是我们的实例化仅仅发生在第一次。一旦被实例化之后,下次判断就不可能为空了,就不会进行实例化了。
但是我们还是会执行同步块,可以减小同步的范围,即在第一次判断为空的时候,我们就加锁以便实例化,接着内部再进行一个判断,如果还为空的话,则进行实例化。这个称之为双重检查锁。
使用双重检查锁进行初始化的实例必须使用Volatile关键字修饰。
7. 实例化的三个步骤
new Singleton的时候是进行了三次操作:
- 第一步是分配内存
- 第二步是初始化对象
- 第三步是指向刚分配的地址
8. 提高获取实例的速度
通过处理,我们可以看到getInstance方法不再是同步方法,它的同步处理也仅仅放在第一次初始化的时候。这样可以提高我们获取实例时候的返回速度。
9. 出现多个实例的情况
单例模式的实例是全局唯一的,但是在以下两种情况下也会出现多个实例:
- 第一个是在分布式系统中,会有多个JVM虚拟机,各个虚拟机都有一个实例。
- 同一个JVM虚拟机使用了多个类加载器同时加载这个类,产生了多个实例。
10. 严重问题
如果它有状态的话,比如它有一个字段(value)等于10,那我在某一个地方用的时候,先把它修改成20,接着我再执行逻辑。但是因为它只有一个实例,所以说其他地方在调用这个单例时候,它也可以随时对这个值进行修改,比如说改成30。
所以说单例模式最佳实践就是无状态的。比如以工具类的形式提供。
11. 优点
- 在内存中只有一个对象,节省内存空间。
- 避免频繁的创建销毁对象,可以提高性能。
- 避免对共享资源的多重占用
- 可以全局访问。
参考资料: