模式介绍
单例模式
是一种常用的软件设计模式,属于创建类型的一种。单例模式的英文全称是Singleton Pattern。它提供了一种创建对象的最佳方式,该模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
请注意,虽然单例模式在某些情况下非常有用,但它也有缺点。例如,它没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
模式特点
单例模式有以下特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
使用单例模式有以下优点:
- 在内存中只有一个实例,大大减少了内存开销。
- 避免对资源的多重占用,例如写文件操作。
使用单例模式有以下缺点:
- 增加了系统开销。每次引用该类实例时都需要检查实例是否存在,这可以通过静态实例来解决。
- 开发人员容易混淆。当使用单例模式的对象时,开发人员必须记住不能使用new关键字来实例化对象。
- 对象生命周期问题。单例模式没有提出对象的销毁,只有单例类拥有对实例的引用。在各种开发语言中,其它类可以销毁对象实例,但这么做将导致单例类内部的指针指向不明。
因此,在设计时需要考虑到这些缺点,权衡使用场景和需求,确保使用单例模式能够满足系统的需求和设计要求。
应用场景
单例模式的应用场景包括:
- Windows的任务管理器(TaskManager):只能打开一个任务管理器实例。
- 回收站(RecycleBin):在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 网站计数器:一般采用单例模式实现,用于同步。
- 应用程序的日志应用:一般都采用单例模式实现,因为共享的日志文件一直处于打开状态,只能有一个实例去操作。
- Web应用的配置对象的读取:一般也应用单例模式,因为配置文件是共享的资源。
- 数据库连接池的设计:一般采用单例模式,因为数据库连接是一种数据库资源。使用数据库连接池主要是节省打开或者关闭数据库连接所引起的效率损耗。
- 多线程的线程池的设计:一般也采用单例模式,因为线程池要方便对池中的线程进行控制。
此外,还有许多其他场景也会使用单例模式,如应用程序中的某些类需要频繁地创建和销毁,或者需要频繁地进行接口调用等。在这些情况下,使用单例模式可以避免不必要的开销和提高程序的性能。
单例类型
单例类型(Singleton Type)是指只能生成一个实例的类或接口。这种类型在某些情况下非常有用,例如需要频繁创建和销毁实例的场景,或者需要确保对资源的独占访问。
在Java中,可以通过以下方式实现单例类型:
- 饿汉式:在类加载时就完成了实例化,避免了线程同步的问题。
- 懒汉式:在类加载时不进行实例化,在第一次调用getInstance方法时才进行实例化,需要考虑线程安全问题。
- 双检锁/双重校验锁(DCL,即 double-checked locking):通过在懒汉式的基础上增加了同步锁,但只在第一次实例化时加锁,之后不再加锁。
- 静态内部类:利用了classloader的机制来保证初始化instance时只有一个线程,是最推荐的方法。
- 枚举:利用了JVM对枚举类型的处理机制,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
请注意,单例类型的具体实现方式可能因编程语言和需求而异。
代码示例
Java实现懒汉模式
在Java中,实现单例模式的方式有多种,以下是一种基于懒汉式的线程安全的单例模式代码示例:
java
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在这个示例中,Singleton
类只有一个私有的构造函数,这意味着外部无法直接通过new Singleton()
来创建该类的实例。该类提供了一个公共的静态方法getInstance()
来获取该类的唯一实例。在这个方法中,首先检查instance
是否为空,如果为空,则通过调用私有构造函数来创建一个新的实例,并将其赋值给instance
变量。否则,直接返回已经存在的instance
。
需要注意的是,这个示例中的getInstance()
方法是同步的,这是为了确保在多线程环境下,只有一个线程能够创建该类的实例。如果不需要考虑线程安全问题,可以将synchronized
关键字去掉,以提高性能。
另外,在实际应用中,还可以使用其他方式来实现单例模式,例如饿汉式、双检锁/双重校验锁、静态内部类、枚举等。具体实现方式应根据具体需求和场景进行选择。
java登记式模式
当然,以下是另一个单例模式的实现方式,这种方式称为"登记式单例模式"。
登记式单例模式类似于静态内部类式,但更加灵活。具体实现思路是不传入参数,直接获取单例对象。首先创建一个类,该类的作用是创建并管理单例控制,这个类被称为单例工厂,包括创建实例的方法和一个保存创建的实例的静态成员。
以下是登记式单例模式的代码示例:
java
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
private SingletonManager() {}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
在这个示例中,SingletonManager
类负责管理所有的单例对象。该类具有一个私有的构造函数,这意味着外部无法直接创建该类的实例。它提供了一个公共的静态方法registerService()
,用于将某个类的唯一实例注册到objMap
中,并为其指定一个唯一的键。另一个公共的静态方法getService()
用于根据指定的键获取已经注册的唯一实例。
要使用这个单例工厂创建唯一实例,需要先调用registerService()
方法将实例注册到工厂中,然后再通过getService()
方法获取该实例。例如:
java
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return (Singleton) SingletonManager.getService("Singleton");
}
// 注册单例对象
static {
SingletonManager.registerService("Singleton", new Singleton());
}
}
在这个示例中,Singleton
类具有一个私有的构造函数和一个公共的静态方法getInstance()
,用于获取该类的唯一实例。在getInstance()
方法中,先通过调用SingletonManager
类的getService()
方法获取已经注册的唯一实例,然后将其强制转换为Singleton
类型并返回。在类加载时,会将一个新的Singleton
实例注册到SingletonManager
中。
python装饰器模式
除了Java中的实现方式外,还有其他编程语言中实现单例模式的示例。
例如,Python中可以使用装饰器或元类来实现单例模式。Python装饰器可以在函数或方法调用前后增加功能,而元类则可以控制类的创建过程。
以下是一个Python装饰器实现单例模式的示例:
python
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Singleton:
pass
在这个示例中,singleton
装饰器定义了一个字典instances
,用于存储每个类的唯一实例。在装饰器中定义了一个get_instance
函数,该函数根据传入的参数创建一个类的实例,并将其存储在instances
字典中。如果该类已经存在实例,则直接返回已经存在的实例。
通过将Singleton
类装饰为@singleton
,可以确保该类只能创建一个唯一实例。
单例模式在spring中的应用
在Spring框架中,单例模式的应用非常广泛。Spring容器中的Bean默认就是单例的。这是因为Spring容器中的Bean默认由Spring容器进行管理,而容器默认使用单例模式。
在Spring中,单例模式的应用主要体现在以下几个方面:
- 管理资源:Spring容器中的Bean默认是单例的,这使得容器可以有效地管理资源,避免资源的浪费和竞争。例如,当需要使用数据库连接等共享资源时,可以将它们放在一个单例Bean中,由Spring容器统一管理和维护。
- 控制事务:Spring框架中的事务管理默认也是单例的。这使得事务可以在多个请求之间共享和重用,提高了事务的性能和可靠性。
- 缓存数据:当需要缓存数据时,可以将数据存储在一个单例Bean中。这样,多个请求可以共享和重用这个单例Bean中的数据,避免了重复获取数据的开销。
- 管理日志和配置信息:在Spring框架中,日志和配置信息通常也是由单例Bean进行管理的。这使得这些信息可以在多个请求之间共享和重用,提高了系统的性能和可靠性。
在Spring框架中,单例模式的应用非常广泛,它可以帮助开发者更好地管理资源、控制事务、缓存数据以及管理日志和配置信息等。