设计模式 --- 单例模式 Singleton
什么是单例模式
- 单例(Singleton)模式的定义:程序运行时,在java虚拟机中只存在该类的一个实例对象
具体指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误
实现思路:
- 将一个类的构造函数声明为 private, 这样外界不能通过构造函数生成新的对象
- 初始化一个static final对象, 然后通过一个static方法返回
- 这样类的实例在类初始化的时候已经生成,不再进行第二次实例化了,而外界只能通过static方法获取singleton对象, 所以这样就保证整个系统只能获取一个类的对象实例。
单例模式不同的创建方法
饿汉模式
- 提前把对象创建好, 用的时候直接用
- 这种模式是最简单最省心的, 在初始化类的时候就生成单例对象, 不足的地方是容易造成资源上的浪费(比如:我事先把面包都做好了,但是你并不一定吃,这样容易造成资源的浪费)
public class Singleton {
//先把对象创建好
private static final Singleton singleton = new Singleton();
private Singleton() {
}
//其他人来拿的时候直接返回已创建好的对象
public static Singleton getInstance() {
return singleton;
}
懒汉模式(lazy initialization)
- 懒汉模式的意思是,我先不创建类的对象实例,等你需要的时候我再创建
**
* 单例模式案例
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
//获取对象的时候再进行实例化
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton
}
}
懒汉模式在并发情况下可能引起的问题
- 懒汉模式解决了饿汉模式可能引起的资源浪费问题,因为这种模式只有在用户要使用的时候才会实例化对象。
- 但是这种模式在并发情况下会出现创建多个对象的情况。因为可能出现外界多人同时访问SingleCase.getInstance()方法,这里可能会出现因为并发问题导致类被实例化多次,所以懒汉模式需要加上锁synchronized (Singleton.class) 来控制类只允许被实例化一次
- 解决思路: 对方法加锁, 如下:
//获取对象的时候再进行实例化
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton
}
- 但是,这种写法每次获取实例都要进行同步(加锁),因此效率较低,并且可能很多同步都是没必要的。
双重检测机制(DCL)
/**
* 单例模式案例
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {//先验证对象是否创建
synchronized (Singleton.class) {//只有当对象未创建的时候才上锁
//上锁之后再检测对象是否创建
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
内部类实现懒汉模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHoler.singleton;
}
//定义静态内部类
private static class SingletonHoler {
//当内部类第一次访问时,创建对象实例
private static Singleton singleton = new Singleton();
}
}
单例模式的应用
-
Windows的Task Manager(任务管理器)就是很典型的单例模式,比如 你能打开两个windows task manager吗?
-
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
-
网站的计数器,一般也是采用单例模式实现,否则难以同步。
-
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
-
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
-
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
-
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
-
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
静态类和单例模式的区别
- 单例模式提供的是一个面向对象的类,静态类只是提供很多静态方法,这些方法不用创建对象,通过类就可以直接调用;
- 如果是一个非常影响性能的对象,单例模式可以懒加载,静态类就无法做到;
- 单例模式可以实现接口,可以继承于别的类
- 静态存储方式的变量存储在内存的静态区,在程序编译时就已经分配好了内存空间。在整个程序运行时,该变量一直占有固有的内存空间,程序结束后才释放该部分内存空间。其中静态局部变量和全局变量存储在静态存储区. 而单例模式的类是动态分配的会被垃圾回收
- 什么时候应该用静态类,什么时候应该用单例模式
- 如果你只是想使用一些工具方法,那么最好用静态类,静态类比单例类更快,因为静态的绑定是在编译期进行的。
- 如果你要维护状态信息,或者访问资源时,应该选用单例模式
单例类例子
- 下面是一个Config类,作用是拿到一些配置信息
- 因为不是一个简单的工具类,而涉及资源的获取和维护所以被设计为单例类
internal class Configs
{
//注意这里使用了C#中的Property保证了getter的功能.
public static Configs Instance { get; } = new();
private readonly provider _provider;
private readonly string _environment;
private Configs()
{
var assemblyPath = Assembly.GetExecutingAssembly().Location;
var configuration = ConfigurationManager.OpenExeConfiguration(assemblyPath);
_environment = configuration.AppSettings.Settings["Environment"].Value;
var consulUri = configuration.AppSettings.Settings["Uri"].Value;
var services = new ServiceCollection();
services.ConfigureHttpClient(ConfigurationResolver.ClientName, new Uri(consulUri), true)
services.ConfigureHttpClient(SecretResolver.ClientName)
_provider = services.BuildServiceProvider();
}
public async Task<string> GetConfigurationValueAsync(string key)
{
return await provider.GetConfigurationValueAsync<string>(key);
}
private async Task<SecureString> GetSecretValueAsync(string key)
{
var resolver = _provider.GetRequiredService<IResolver>();
return await resolver.GetSecretAsync(_environment, key);
}
}
以下是一个示例代码,展示了一个不应该被设计为静态类而应该被设计成单例的情况:
public class DatabaseConnection {
private static DatabaseConnection instance;
private String connectionString;
private DatabaseConnection() {
// 私有构造函数
connectionString = "jdbc:mysql://localhost:3306/mydatabase";
// 执行其他初始化操作...
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void connect() {
// 连接数据库
System.out.println("Connected to database: " + connectionString);
}
public void disconnect() {
// 断开数据库连接
System.out.println("Disconnected from database: " + connectionString);
}
// 其他数据库操作方法...
}
这个类的设计不适合使用静态类,因为数据库连接是一个具有状态的对象 并且可能需要在应用程序的不同部分进行访问和管理。静态类的方法和属性通常是全局共享的,并且不适合维护对象的状态。
相反,将DatabaseConnection 设计为单例类更合适。使用单例模式可以确保在应用程序中只有一个数据库连接实例,并提供全局访问点,以便其他部分可以共享和使用该实例。