
简介
单例模式,也叫单件模式。顾名思义,就是程序中只有一个实例。一般在我们实际工作中,会经常遇到这个模式,所以,掌握这个模式是应该的。
定义
单件方式可以有很多中方法定义,可根据不同的应用场景来选择最合适的定义方式。根据目前所学习的知识,可分为以下五种,某些可能在多线程的环境下回出现安全问题,需要改进。
《Head Filst 设计模式》中定义为:
单件模式确保程序中一个类只有一个实例,并且能够提供访问这个实例的全局访问点。
饥汉式
也叫饿汉式,二话不说 ,当jvm加载类时,就将类的静态实例初始化。
1 | public class SingleInstanceT00 { |
在没有空间限制的情况下,可以选择这样使用。但是更优雅的做法是,调用的时候进行初始化,也就是延迟初始化。
延迟初始化
延迟初始化会带来线程安全的问题,比如改造后的代码为:
1 | public class SingleInstanceT01 { |
经测试后发现,多个线程同时进入getInstance后,得到的不是同一个实例。
为了使得线程安全,解决方案也分为很多种,有synchronized同步方法、双重检查锁定和静态内部类初始化三种。
同步方法
使用synchronized对getInstance方法进行同步处理,但会导致不必要的性能开销。如果getInstance()方法被多个线程频繁调用,将会导致程序执行的性能下降。只有不频繁调用的时候,可能是理想状态。实现方式为:
1 | public class SingleInstanceT02 { |
使用synchronized 同步方法时可以不用加volatile,因为synchronized 可以保证原子性。
双重检查锁定
双重检查锁定使用synchronized同步代码块的方式成功的优化了性能。只有第一次进来的时候,才需要加锁。其他时候进来的时候先判断instance是否为空,如果不为空的话,就直接进行返回。
1 | public class SingleInstanceT03 { |
注意:加volatile是为了防止指令的重排序,在jvm执行指令时,instance = new SingleInstanceT03()
分了三步:
第一步:给对象分配内存空间
第二步:成员变量初始化
第三步:将instance指向刚分配的内存地址(此时还是半初始化状态,有属性的话,属性尚未赋值)
如果没有加volatile,第二步和第三步会交换顺序执行,导致其他线程拿到的是半初始化的实例,拿到未初始化的数据(比如秒杀系统拿到的值为0,带来重大安全隐患)。
类初始化
这也是延迟初始化的一种,基于类初始化实现的。实现原理是每个对象对应有一个初始化锁,初始化时线程需要获取该对象对应的初始化锁。如果没有获取,那么线程必须等到初始化锁释放了才能获取。
1 | public class SingleInstanceT04 { |
应用
在《Head Filst 设计模式》提到,使用单件模式的地方很多。常见的有线程池、连接池、缓存、日志对象。
还用一些不常见的,对话框、处理偏好设置、注册表的对象、充当打印机、显卡等设备的驱动程序的对象。