大话设计模式之单例模式详解

应用场景

在学习单例设计模式前,我想应该先去了解这种模式是在什么样的背景下产生的,这有利于后续学习时的理解。记得我自己最先真正使用到单例模式,是在进行数据库连接的时候,当时在做原生项目的时候,因为需要经常访问数据库,就会常常打开和关闭数据库连接,这种方式会导致项目的效率下降。在这种场景下,如果使用一个类,来对数据库资源的连接进行封装,保证在整个项目中,只有一个该类提供的对象,就可以避免上述的问题。所以,单例模式的作用也就体现出来了,当然它的应用场景还有许多,例如项目日志输出,统计网站在线人数等。

定义

单例模式最初的定义出现于《设计模式》(艾迪生维斯理,1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”在Java中单例模式的定义是:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

实现思路

单例模式分为饿汉式和懒汉式两种,两者实现的思路相同:
1.在单例类中,提供一个私有静态属性,用于接收实例化对象;
2.在单例类中,构造方法必须私有化,防止在类外进行实例化;
3.在单例类中,向类外提供一个获取类对象的公有静态方法。

代码实现

饿汉式单例模式实现

饿汉式单例模式:在系统加载类的时候就会创建类的对象,并保存在类中。它的特点是线程安全,以空间换时间。

public class Singleton {
    //1、私有静态属性
    private static Singleton instance = new Singleton();
    //2、私有构造方法
    private Singleton() {
        System.out.println("***创建Singleton类对象***");
    }
    //3、公有静态方法
    public static Singleton getInstance() {
        return instance;
    }
    //测试
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}
//程序执行结果
***创建Singleton类对象***
true

懒汉式单例模式实现

懒汉式单例模式:在系统加载类的时候不创建类的对象,直到第一次使用类的对象时才会进行创建。它的特点是线程不安全,以时间换空间。

public class Singleton {
    //1、私有静态属性
    private static Singleton instance = null;
    //2、私有构造方法
    private Singleton() {
        System.out.println("***创建Singleton类对象***");
    }
    //3、公有静态方法
    public static Singleton getInstance() {
        //判断类对象是否为空,若为空则创建类对象
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    //测试
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}
//程序执行结果
***创建Singleton类对象***
true

懒汉式单例模式是存在线程风险的,下面我们来测试一下,是不是像说的那样真的存在线程风险?

public class Singleton {
    //1、私有静态属性
    private static Singleton instance = null;
    //2、私有构造方法
    private Singleton() {
        System.out.println("【" + Thread.currentThread().getName() + "】***创建Singleton类对象***");
    }
    //3、公有静态方法
    public static Singleton getInstance() {
        //判断类对象是否为空,若为空则创建类对象
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    public void print() {
        System.out.println("**执行print()方法**");
    }
    //测试
    public static void main(String[] args) {
        //创建三个线程对象并运行
        for(int i = 0; i < 3; i++){
           new Thread(() -> {
               //在线程运行过程中,获取Singleton类对象
               Singleton.getInstance().print();
           }, "线程对象 - " + i).start();
        }
    }
}
//程序执行结果
【线程对象 - 0***创建Singleton类对象***
【线程对象 - 1***创建Singleton类对象***
**执行print()方法**
【线程对象 - 2***创建Singleton类对象***
**执行print()方法**
**执行print()方法**

通过程序的执行结果,发现当有了若干个线程对象,当前的程序就可以产生多个Singleton类的对象。显然,这不符合单例模式的特点,因为单例模式要求在程序的整体运行中只产生一个实例化对象。造成上述问题的关键是,代码本身出现了不同步的情况,而解决的核心就在于进行同步处理。

懒汉式单例模式改进

实现代码同步,我们需要使用到sychronized关键字。改进方法一,直接对公有静态方法做同步处理:

public static synchronized Singleton getInstance() {
    //判断类对象是否为空,若为空则创建类对象
    if(instance == null){
        instance = new Singleton();
    }
    return instance;
}
//程序执行结果
【线程对象 - 0***创建Singleton类对象***
**执行print()方法**
**执行print()方法**
**执行print()方法**

按照上述方法,我们的确实现了单例模式的要求,保证在程序整体运行过程中,单例类只提供一个实例化的对象。但是,使用这种方法做同步处理,效率会比较低,因为在整个方法中只有一个地方需要做同步处理,下面我们使用同步代码块进行更合理的同步处理。

public static Singleton getInstance() {
    //判断类对象是否为空,若为空则创建类对象
    if(instance == null){
        //同步代码块,静态方法中不允许使用this关键字,这里使用类的Class对象
        synchronized(Singleton.class){
            if(instance == null){
                instance = new Singleton();
            }
        }
    }
    return instance;
}
//程序执行结果
【线程对象 - 0***创建Singleton类对象***
**执行print()方法**
**执行print()方法**
**执行print()方法**

通过对“判断instance对象是否为空”作同步处理,这种方法可以减小同步产生的代价,使程序的执行效率更高一些。
懒汉式单例模式实现,使用这种方法也被称为DCL(双重检查加锁)。另外,为了在程序性能上再做一点提升,可以在单例模式的属性声明上使用volatile关键字,例如:

private static volatile Singleton instance = null;

到达文末了,十分感谢小伙伴们的阅读哈,如果您对我的文章有什么意见或者建议,也欢迎您来进行评论,私信我也可以哈!
代码如诗,小伙伴们和我一起努力加油,做持续学习者!:)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章