设计模式 --- 单例模式 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();
	 }
}

单例模式的应用

  1. Windows的Task Manager(任务管理器)就是很典型的单例模式,比如 你能打开两个windows task manager吗?

  2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。

  4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

  5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

  6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

  7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

  8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

静态类和单例模式的区别

  • 单例模式提供的是一个面向对象的类,静态类只是提供很多静态方法,这些方法不用创建对象,通过类就可以直接调用;
  • 如果是一个非常影响性能的对象,单例模式可以懒加载,静态类就无法做到;
  • 单例模式可以实现接口,可以继承于别的类
  • 静态存储方式的变量存储在内存的静态区,在程序编译时就已经分配好了内存空间。在整个程序运行时,该变量一直占有固有的内存空间,程序结束后才释放该部分内存空间。其中静态局部变量和全局变量存储在静态存储区. 而单例模式的类是动态分配的会被垃圾回收
  • 什么时候应该用静态类,什么时候应该用单例模式
  • 如果你只是想使用一些工具方法,那么最好用静态类,静态类比单例类更快,因为静态的绑定是在编译期进行的。
  • 如果你要维护状态信息,或者访问资源时,应该选用单例模式

单例类例子

  • 下面是一个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 设计为单例类更合适。使用单例模式可以确保在应用程序中只有一个数据库连接实例,并提供全局访问点,以便其他部分可以共享和使用该实例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值