一、善用设计模式
设计模式是前人工作的总结和提炼。通常,被人们广泛流传的设计模式都是对某一特定问题的成熟解决方案。如果能合理地使用设计模式,不仅能使系统更容易被他人理解,同时也能使系统拥有更加合理的结构。
1.1 单例模式
单例模式是设计模式中使用最为普遍的模式之一,它是一种对象创建模式,用于产生一个对象的具体实例,可以确保系统中一个类只产生一个实例。
单例模式2大好处
(1)对于频繁使用的对象,可以省去new操作花费的时间,这对于那些重量级对象而言,是一笔非常可观的系统开销。
(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
单例模式的角色
- 单例类
- 使用者
角色 | 作用 |
---|---|
单例类 | 提供单例工厂,返回单例 |
使用者 | 获取并使用单例类 |
单例模式结构
单例模式的核心
通过一个接口返回唯一的对象实例。
代码
Demo1
首先单例类必须要有一个private访问级别的构造函数,只有这样,才能确保单例不会在系统的其他代码内被实例化,这一点是相当重要的。
其次,instance成员变量和getInstance()方法必须是static的。
评价:这种单例的实现方式非常简单,而且十分可靠,唯一的不足仅是无法对instance做延迟加载。
假如单例的创建过程很慢,而由于instance成员变量是static定义的,因此在JVM加载单例类时,单例对象就会被建立,如果此时这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。
为了解决这个问题,并提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制。
Demo2
首先,对于静态成员变量instance赋予初始值null,确保系统启动时没有额外的负载。
其次,在getInstance()工厂方法中,判断当前单例是否已经存在,若存在,则返回,不存在,再建立单例。
这里尤其要注意,getInstance()方法必须是同步的,否则在多线程环境下,当线程1正新建单例完成赋值操作前,线程2可能判断instance为null,故线程2也将启动新建单例的程序,从而导致多个实例被创建,因此同步关键字是必需步骤。
评价:使用上例中的单例,虽然实现了延迟加载的功能,但和第一种方法相比,它引入了同步关键字,因此在多线程环境中,它的时耗要远远大于第一种单例模式的时耗。以下测试代码就说明了这个问题。
为了使用延迟加载引入的同步关键字反而降低了系统性能,是不是有点得不偿失呢?为了解决这个问题,还需要对其进行以下改进:
Demo3
在这个实现中,单例模式使用内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保当StaticSingleton类被载入JVM时不会初始化单例类,而当getInstance()方法被调用时才会加载SingletonHolder,从而初始化instance。
同时,由于实例的建立是在类加载时完成的,故天生对多线程友好,getInstance()方法也不需要使用同步关键字。因此,这种实现方式同时兼备以上两种实现方式的优点。
评价:使用内部类的方式实现单例,既可以做到延迟加载,也不必使用同步关键字,是一种比较完善的单例实现方式。
注意:序列化和反序列化可能会破坏单例。
一般来说,对单例进行序列化和反序列化的场景并不多见,但如果存在,就要多加注意。