一、引言
本篇文章是源于工作中遇到了一个与单例模式相关的问题。并由此对单例模式作一个复习总结。
二、背景
在一次开发过程中,控制台抛出了一个关于写入数据库时,主键冲突的异常。最后排查问题后主要是因为以下三点:
1)获取数据库连接的方式并不是单例的;
2)加载页面时同时请求了相同的接口两次;
3)主键采用的是自动增长。
当然这里若是需要快速解决bug,只需解决问题2,既加载页面时只请求一次该接口。
但是问题1才是潜藏更深的隐患。因为较复杂的业务将会占用过多的多余连接,影响系统性能,并可能再次导致上述问题。
三、总结
1、什么是单例模式?
在了解单例模式之前,我们应该先要明白两个问题,什么叫设计模式,设计模式与单例模式又是怎样的关系。
设计模式是面向对象的软件开发员,通过漫长的试验和错误总结出来的一套解决一般问题的方案。
而单例模式,就是其中一种针对一类问题的解决方案。
这就好比设计模式是独孤九剑,而单例模式和其它不同的设计模式就好比破剑式、破刀式。
破剑式就是破解普天下各门各派剑法的招式。那么单例模式又是解决什么问题的呢?并且是如何解决的呢?
单例模式提供了一种最佳的创建对象的方式:
一个单一的类,负责创建自己的对象,同时确保只创建一个对象,并且提供了唯一一个访问此对象的方法,且不需要再实例化该对象。
2、使用场景
现在已经明白单例模式使用的要点既,一个类仅一个实例,并且提供访问对象唯一全局方法。
那么单例模式主要解决的是什么呢?
一个全局的类频繁的创建和消耗。
如何解决的呢?
判断是否已有其单例,有则返回,没有则创建。
何时使用呢?
当我们想控制实例化数量,节省系统资源时。
现实场景:
1)计划生育,一对夫妻只有一个孩子;
2)windows操作一个文件时,多次打开,始终只有这一个文件。
抽象到程序中的使用场景:
1)生成唯一序列号;
2)页面上的计数器;
3)创建对象需要消耗比较多的资源,比如IO和数据库的连接。
3、实现方式
这里并不会详细记录其实现步骤(详细步骤可以参考第四大点中的链接),此处仅记录实现单例模式的几种具体方法与各自优劣。
3.1、懒汉式(线程不安全)
示例:
public class Single4Lazy {
private static Single4Lazy instance;
private Single4Lazy() {
}
public static Single4Lazy getInstance() {
if(instance == null) {
instance = new Single4Lazy();
}
return instance;
}
}
优点:实现简单,懒加载。
缺点:线程不安全(没有加锁),严格来说并不能算单例模式。
3.2、懒汉式(线程安全)
示例:
public class Single4LazySynchronized {
private static Single4LazySynchronized instance;
private Single4LazySynchronized() {
}
public static synchronized Single4LazySynchronized getInstance() {
if (instance == null) {
instance = new Single4LazySynchronized();
}
return instance;
}
}
优点:实现简单,线程安全(需要加锁),懒加载。
缺点:效率低(getInstance()方法不能频繁使用,否则影响效率)。
3.3、饿汉式
示例:
public class SingleObject {
/**
* 创建一个私有的单例对象
*/
private static SingleObject instance = new SingleObject();
/**
* 构造方法设为私有,这样就不会实例化对象
*/
private SingleObject() {
}
/**
* 获取唯一可用对象
*/
public static SingleObject getInstance(){
return instance;
}
public void showMessage() {
System.out.println("Hello, World ! ");
}
优点:线程安全,实现简单,效率较高(不用加锁)。
缺点:非懒加载,容易生成垃圾对象。
3.4、双检锁(DCL, double-checked locking)
示例:
public class Single4DCL {
private static Single4DCL instance;
private Single4DCL() {
}
public static Single4DCL getInstance() {
if (instance == null) {
/**
* getInstance()是静态方法,所以不能使用未静态或未实例的类对象
*/
synchronized (Single4DCL.class) {
if (instance == null) {
instance = new Single4DCL();
}
}
}
return instance;
}
}
优点:线程安全,懒加载,效率较高。
缺点:JDK1.5起,实现较复杂,getInstance()性能对应用程序很关键。
3.5、静态内部类
示例:
public class Single4Static {
private static class Single4StaticHolder {
private static final Single4Static INSTANCE = new Single4Static();
}
private Single4Static() {
}
public static Single4Static getInstance() {
return Single4StaticHolder.INSTANCE;
}
}
优点:线程安全,效率较高,懒加载,实现难度一般。
缺点:只适用于静态域情况。
3.6、枚举
示例:
public enum Single4Enum {
INSTANCE;
public void whateverMethod() {
}
}
优点:线程安全,最佳实现,自动支持序列化机制,绝对防止多次实例化,不能通过reflection attack来调用私有构造方法
缺点:JDK1.5起
4、小tips
构造器是私有的。
通常不使用第1、2种,一般使用第3种,只有明确需要懒加载时使用第5种,
需要反序列化创建时可以尝试第6种,有其它特殊需求时,可以考虑第4种。
四、参考
https://www.runoob.com/design...
https://www.runoob.com/design...
https://baike.baidu.com/item/...
五、最后
若有不足,欢迎指正。
求知若渴,虚心若愚。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。