设计模式之单例模式
什么是单例模式?
单例模式是一种对象创建型模式。所谓创建型模式就是将对象的创建和使用分离,在使用对象时无需关心对象的创建细节,从而降低系统的耦合度,使得设计方案更易于修改和扩展。
单例模式三个要点:(1)某个类只能有一个实例。(2)必须自行创建这个实例。(3)必须自行向整个系统提供这个实例。
饿汉式单例类
类加载进来就直接实例化对象,无需考虑多线程安全问题,但是浪费资源严重。
1 | public class EagerSingleton { |
懒汉式单例类
在第一次调用 getInstance()
方法时实例化,类加载时不自行实例化,在需要的时候再加载实例。但在多线程下会出现线程安全问题,可能会创建多个 instance 对象,违背了单例模式的初衷。
1 | public class LazySingleton { |
为解决多线程问题,可以采用对 getInstance()
方法进行同步,但每次调用getInstance()
方法都需要进行线程锁定判断,比较浪费资源,尤其在高并发访问环境下会导致系统性能大大降低。
1 | public class LazySingleton { |
事实上,无需对整个 getInstance()
方法锁定,只需要锁定代码 “ instance = new LazyInstance() ”。这种方法解决了浪费资源问题,但是在多线程下依然可能出现实例对象不唯一。原因在于:假如某一瞬间线程 A 和线程 B 都在调用getInstance()
方法,此时 instance 对象为 null,均能通过 “ instance == null ” 的判断。线程 A 进入 synchronized 锁定的代码中执行实例创建代码,线程 B 处于排队等待状态。当 A 执行完毕创建了实例后,线程 B 进入 synchronized 代码,此时 B 并不知道实例已经创建,将创建新的实例。
1 | public class LazySingleton { |
因此还得进行改进,在 synchronized 锁定代码中再进行一次 “ instance == null ” 判断,这种方式称为双重检查锁定 。需要注意的是在需要在静态成员变量前加 volatile
修饰符。但 volatile
关键字会屏蔽 Java 虚拟机做的一些代码优化,导致系统运行效率降低。
1 | public class LazySingleton { |
IoDH
饿汉式不能实现延迟加载,不管用不用,它始终占据内存;懒汉式 线程控制麻烦,而且性能受到影响。一种被称为 Initialization on Demand Holder(IoDH)
的方法能够克服这些缺点。
静态单例对象没有作为 Singleton 的成员变量直接实例化,因此类加载时不会实例化 Singleton,第一次调用 getInstance()
时加载内部类 HolderClass
,初始化 instance
,由 Java 虚拟机保证其线程安全性,确保其只能初始化一次。
通过使用 IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。
1 | public class Singleton { |