静态内部类实现mysql连接_单例模式 - 只有一个实例

程序在运行的时候,通常会有很多的实例。例如,我们创建 100 个字符串的时候,会生成 100 个 String 类的实例。

但是,有的时候,我们只想要类的实例只存在一个。例如,「你猜我画」中的画板,在一个房间中的用户需要共用一个画板实例,而不是每个用户都分配一个画板的实例。

此外,对于数据库连接、线程池、配置文件解析加载等一些非常耗时,占用系统资源的操作,并且还存在频繁创建和销毁对象,如果每次都创建一个实例,这个系统开销是非常恐怖的,所以,我们可以始终使用一个公共的实例,以节约系统开销。

像这样确保只生成一个实例的模式,我们称之为 单例模式。

如何理解单例模式

单例模式的目的在于,一个类只有一个实例存在,即保证一个类在内存中的对象唯一性。

现在,我们来理解这个类图。

d16d5d55d21e09f47b47f2b962570734.png

静态类成员变量

Singleton 类定义的静态的 instance 成员变量,并将其初始化为 Singleton 类的实例。这样,就可以保证单例类只有一个实例。

私有的构造方法

Singleton 类的构造方法是私有的,这个设计的目的在于,防止类外部调用该构造方法。单例模式必须要确保在任何情况下,都只能生成一个实例。为了达到这个目的,必须设置构造方法为私有的。换句话说,Singleton 类必须自己创建自己的唯一实例。

全局访问方法

构造方法是私有的,那么,我们需要提供一个访问 Singleton 类实例的全局访问方法。

简要定义

保证一个类只有一个实例,并提供一个访问它的全局访问方法。

单例模式的实现方式

饿汉式

顾名思义,类一加载对象就创建单例对象。

8b8900cff54a56e2d270cb6384f392d1.png

值得注意的是,在定义静态变量的时候实例化 Singleton 类,因此在类加载的时候就可以创建了单例对象。

此时,我们调用两次 Singleton 类的 getInstance() 方法来获取 Singleton 的实例。我们发现 s1 和 s2 是同一个对象。

0a6dc65e5df3b705501eed49c3b3f910.png

懒汉式

懒汉式,即延迟加载。单例在第一次调用 getInstance() 方法时才实例化,在类加载时并不自动实例化,在需要的时候再进行加载实例。

325e0c03859b570e2d25b4eb639bb51e.png

懒汉式的线程安全

在多线程中,如果使用懒汉式的方式创建单例对象,那就可能会出现创建多个实例的情况。

为了避免多个线程同时调用 getInstance() 方法,我们可以使用关键字 synchronized 进行线程锁,以处理多个线程同时访问的问题。每个类实例对应一个线程锁, synchronized 修饰的方法必须获得调用该方法的类实例的锁方能执行, 否则所属线程阻塞。方法一旦执行, 就独占该锁,直到从该方法返回时才将锁释放。此后被阻塞的线程方能获得该锁, 重新进入可执行状态。

8814090e323802b1a8c9149928b405a5.png

上面的案例,在多线程中很好的工作而且是线程安全的,但是每次调用 getInstance() 方法都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能下降。事实上,不仅效率很低,99%情况下不需要线程锁定判断。

这个时候,我们可以通过双重校验锁的方式进行处理。换句话说,利用双重校验锁,第一次检查是否实例已经创建,如果还没创建,再进行同步的方式创建单例对象。

7bd67931f94cb7dea9d12e8fae04d1b4.png

枚举

枚举的特点是,构造方法是 private 修饰的,并且成员对象实例都是预定义的,因此我们通过枚举来实现单例模式非常的便捷。

ec6c0b32e69f959b185f0e774561afcf.png

静态内部类

类加载的时候并不会实例化 Singleton5,而是在第一次调用 getInstance() 加载内部类 SigletonHolder,此时才进行初始化 instance 成员变量,确保内存中的对象唯一性。

b41bd0e25d30e25c0a0e0e388194f503.png

思维发散

如何改造成单例类

假设,我们现在有一个计数类 Counter 用来统计累加次数,每次调用 plus() 方法会进行累加。

65a1f23b72fa11f53f285a1efa398e19.png

这个案例的实现方式会生成多个实例,那么我们如何使用单例模式确保只生成一个实例对象呢?

实际上,拆解成3个步骤就可以实现我的需求:静态类成员变量、私有的构造方法、全局访问方法。

b1e089873b8430832ab2d5e9948298bc.png

多例场景

基于单例模式,我们还可以进行扩展改造,获取指定个数的对象实例,节省系统资源,并解决单例对象共享过多有性能损耗的问题。

我们来做个练习,我现在有一个需求,希望实现最多只能生成 2 个 Resource 类的实例,可以通过 getInstance() 方法进行访问。

be337200b183750db8c56b8dc19f5637.png

单例模式 vs 静态方法

如果认为单例模式是非静态方法。而静态方法和非静态方法,最大的区别在于是否常驻内存,实际上是不对的。它们都是在第一次加载后就常驻内存,所以方法本身在内存里,没有什么区别,所以也就不存在静态方法常驻内存,非静态方法只有使用的时候才分配内存的结论。

因此,我们要从场景的层面来剖析这个问题。如果一个方法和他所在类的实例对象无关,仅仅提供全局访问的方法,这种情况考虑使用静态类,例如 java.lang.Math。而使用单例模式更加符合面向对象思想,可以通过继承和多态扩展基类。此外,上面的案子中,单例模式还可以进行延伸,对实例的创建有更自由的控制。

单例模式与数据库连接

数据库连接并不是单例的,如果一个系统中只有一个数据库连接实例,那么全部数据访问都使用这个连接实例,那么这个设计肯定导致性能缺陷。事实上,我们通过单例模式确保数据库连接池只有一个实例存在,通过这个唯一的连接池实例分配 connection 对象。

总结

单例模式的目的在于,一个类只有一个实例存在,即保证一个类在内存中的对象唯一性。

如果采用饿汉式,在类被加载时就实例化,因此无须考虑多线程安全问题,并且对象一开始就得以创建,性能方面要优于懒汉式。

如果采用懒汉式,采用延迟加载,在第一次调用 getInstance() 方法时才实例化。好处在于无须一直占用系统资源,在需要的时候再进行加载实例。但是,要特别注意多线程安全问题,我们需要考虑使用双重校验锁的方案进行优化。

实际上,我们应该采用饿汉式还是采用懒汉式,取决于我们希望空间换取时间,还是时间换取空间的抉择问题。

此外,枚举和静态内部类也是非常不错的实现方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值