模式动机
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但只能有一个正在工作的任务。保证一个类只有一个实例对象就是单例模式的动机。一个好的解决方法是,让类负责保存它唯一的实例,这个类可以保证没有其他实例被创建,并且可以提供一个访问该实例的方法。
模式定义
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局的方法。
单例模式的三大要点:
- 某个类只能有一个实例
- 这个类必须自行创建这个实例
- 该类必须自行向整个系统提供这个实例
模式结构
私有成员变量:Singleton
静态public方法:getInstance()、singletonOperation()
私有构造方法:Singleton(),实例化Singleton类
时序图
代码实现
//线程不安全,懒汉单例模式
/**
* 优点:懒加载启动快,资源占用小,使用时才实例化,无锁。
* 缺点:非线程安全。
**/
public class lazySingleton {
private static lazySingleton singleton=null;
private lazySingleton(){
}
public static lazySingleton getInstance(){
if(singleton==null){
singleton=new lazySingleton();
}
return singleton;
}
}
//懒汉式单例,但是线程安全
/**
* 优点:懒加载启动快,资源占用小,使用时才实例化,有锁。
*
* 缺点:synchronized 为独占排他锁,并发性能差。即使在创建成功以后,获取实例仍然是串行化操作。
*
**/
public class safeLazySingleton {
private static safeLazySingleton singleton=null;
private safeLazySingleton(){
}
//加上synchronized,多个线程调用同一个对象的同步方法(synchronized关键字修饰的方法)时会阻塞
public static synchronized safeLazySingleton getInstance(){
if(singleton==null){
singleton=new safeLazySingleton();
}
return singleton;
}
}
/**
* 懒汉模式--双重加锁检查DCL(Double Check Lock)
* 优点:懒加载,线程安全。
*
**/
public class DCLLazySingleton {
private static DCLLazySingleton singleton=null;
private DCLLazySingleton(){
}
//加上synchronized锁
public static synchronized DCLLazySingleton getInstance(){
if(singleton==null){
//对DCLLazySingleton类加锁,多个线程实例化该类时会阻塞。
synchronized(DCLLazySingleton.class){
if(singleton==null) {
singleton = new DCLLazySingleton();
}
}
}
return singleton;
}
}
/**
*
* 饿汉单例模式
* 优点:天生是线程安全的,使用时没有延迟。
* 缺点:启动时即创建实例,启动慢,有可能造成资源浪费。
**/
public class HungerSingleton {
private static HungerSingleton singleton=new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getInstance(){
return singleton;
}
}
//将懒加载和线程安全完美结合的一种方式(无锁)。(推荐)
/*加载时无需创建初始化私有成员变量(懒加载),在实例化时调用内部静态类,实例化单例对象,天生线程安全
* */
public class HolderSingleton {
//类的内部静态类,该内部类与外部类的实例没有绑定关系,只有被调用时才会被加载,实现了延迟加载
private static class SingletonHolder{
private static HolderSingleton singleton=new HolderSingleton();
}
private HolderSingleton(){
}
public static HolderSingleton getInstance(){
return SingletonHolder.singleton;
}
}
模式分析
单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类——Singleton
。单例类拥有一个私有构造函数
,确保用户无法通过new关键字直接实例化它。除此之外,该模式包含一个静态私有成员变量
与静态公有的工厂方法
。该工厂方法检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
所以,单例模式的实现要注重如下三点:
- 单例类的构造函数为私有;
- 提供一个自身的静态私有成员变量;
- 提供一个公有的静态工厂方法。
模式的优缺点
优点
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此
可以节约系统资源
,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。 - 允许可变数目的实例。基于单例模式进行扩展,可以获得指定数目的对象实例
缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的
职责过重
,在一定程度上违背了“单一职责原则”
。因为单例类既充当了工厂角色
,提供了工厂方法,同时又充当了产品角色
,包含一些业务方法,将产品的创建
和产品的本身的功能
融合到一起。 - 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的
对象长时间不被利用
,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化
,这将导致对象状态的丢失
。
模式实例
- 数据库主键编号生成器。数据库只能有一个地方分配下一个主键编号。
- 操作系统中的打印池,一个系统中只允许运行一个打印池对象
参考
https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/singleton.html