文章目录
详解Java设计模式之单例模式
案例引出单例模式
可以看到,在正常情况下只能打开唯一的一个任务管理器!
那么?如何去保证一个类只有一个实例并且这个实例易于被访问呢?
- 使用全局变量:可以确保对象随时可以被访问,但不能防止创建多个对象
- 让类自身负责创建和保存它的唯一实例,并保证不能通过其他方法创建实例,同时提供一个访问该实例的方法。
单例模式
定义
单例模式:确保一个类只有一个实例,并提供一个全局的访问点来访问这个唯一的实例,
Singleton Pattern: Ensure a class has only one instance,and provide a golbal point of access to it.
-
单例模式也属于对象创建型模式
-
要点
- 某个类只能有一个实例
- 必须自行创建这个实例
- 必须自行向整个系统提供这个实例
模式UML图表示
单例模式结构
- 单例模式只包含一个单例角色:Singleton(单例)
- 单例模式的实现要点
- 私有的构造函数
- 静态私有的成员变量(自身类型)
- 静态共有的工厂方法
案例分析
某软件公司承接了一个服务器负载均衡(Load Balance)软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高了系统的整体处理能力,缩短了响应时间。由于集群中的服务器需要动态删减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,只能有一个负载均衡器来负责服务器的管理和请求的分发,否则将会带来服务器状态的不一致以及请求分配冲突等问题。
如何确保负载均衡器的唯一性是该软件成功的关键,试使用单例模式设计服务器负载均衡器。
案例类图
相关代码
相关代码见我的Gitte
src/csu/edu/cn/designpattern/singletonpattern/normal · PlusHuang/HelloWord - 码云 - 开源中国 (gitee.com)
结果分析:虽然出现了四个负载均衡器对象,但是实际上是同一个对象
csu.edu.cn.designpattern.singletonpattern.normal.LoadBalancer@1b6d3586
csu.edu.cn.designpattern.singletonpattern.normal.LoadBalancer@1b6d3586
负载均衡器具有唯一性!!
分发请求至:Server 2
分发请求至:Server 3
分发请求至:Server 3
分发请求至:Server 4
分发请求至:Server 5
分发请求至:Server 2
分发请求至:Server 5
分发请求至:Server 3
分发请求至:Server 2
分发请求至:Server 5
分发请求至:Server 3
分发请求至:Server 5
分发请求至:Server 5
分发请求至:Server 2
分发请求至:Server 1
饿汉式单例
UML表示
核心代码
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
分析
当该单例类被加载时,静态变量instance会被初始化,此时的私有构造函数会被调用,单例的唯一实例会被创建。
懒汉式单例
UML表示
**懒汉式单例在第一次被引用即调用getInstance方法时将自己实例化,在懒汉式单例中,在被加载时并不会将自己实例化。**这种技术被称为延迟加载(Lazy Load)技术,需要时才加载。
饿汉式代码-基础版(延迟加载)
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
当多个线程同时访问将导致创建多个单例对象?如何解决
饿汉式代码-改进版锁方法
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
上述改进方法在方法声明前加了关键字synchronized进行线程锁定,已处理多个线程同时访问的问题。虽然解决了线程安全问题,但是在每次调用时都需要进行线程锁定判断。在多线程高并发访问条件下会导致系统性能大大降低。所以仍需改进。
饿汉式代码-改进版锁代码段
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
解决了锁定方法导致系统性能下降的问题,但是这种锁定时没有起到任何作用的会创建出多个对象。
饿汉式代码-改进版之双重检查锁定
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
使用双重检查锁定需要在静态成员变量前加上volatile关键字,是为了取消虚拟机所做的一些代码优化,并且只能在JDK1.5上面执行。
饿汉式与懒汉式的比较
- 饿汉式单例类:无须考虑多个线程同时访问的问题;调用速度和反应时间优于懒汉式单例;资源利用效率不及懒汉式单例;系统加载时间可能会比较长
- 懒汉式单例类:实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制,将导致系统性能受到一定影响
模式优点
- 提供了对唯一实例的受控访问
- 可以节约系统资源,提高系统的性能
- 允许可变数目的实例(多例类)
模式缺点
- 扩展困难(缺少抽象层)
- 单例类的职责过重
- 由于自动垃圾回收机制,可能会导致共享的单例对象的状态丢失
模式适用环境
- 系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例