一、引言

本篇文章是源于工作中遇到了一个与单例模式相关的问题。并由此对单例模式作一个复习总结。

二、背景

在一次开发过程中,控制台抛出了一个关于写入数据库时,主键冲突的异常。最后排查问题后主要是因为以下三点:
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/...

五、最后

若有不足,欢迎指正。
求知若渴,虚心若愚。

MO_or
25 声望75 粉丝

小菜鸟的成长地。