Java-设计模式(单例模式)

本文介绍了设计模式的基本概念,它是一套被广泛使用的代码设计经验总结,分为创建型、结构型和行为型三种。重点讲解了单例模式,这是一种保证类只有一个实例的创建型模式,常用于需要频繁实例化然后销毁的对象。单例模式有饿汉式和懒汉式两种实现方式,其中双重检查锁模式是线程安全且高效的实现。文章还探讨了单例模式的优缺点及其在实际应用中的选择。

一、什么是设计模式

        软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用多数人知晓的代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

二、设计模式的种类23种

  • 创建型模式

    用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。

  • 结构型模式

    用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。

  • 行为型模式

    用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。

三、单例设计模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

1.单例模式的结构

单例模式的主要有以下角色:

  • 单例类。只能创建一个实例的类.

  • 访问类。使用单例类

2.单例模式的实现

单例设计模式分类两种:

​ 饿汉式:类加载就会导致该单实例对象被创建

​ 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

​ (1)饿汉式(静态变量方式) :

class Singleton{
    //私有化构造方法
    private Singleton(){};
    //创建本本类对象
    //static:随着类的加载会被加载到内存中且只会被加载一次
    private static Singleton singleton =new Singleton();

    //静态方法 返回本类对象
    public static  Singleton getInstance(){
        return singleton;
    }

}

        该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

(2)懒汉式(双重检查锁 ):

class Singleton03{
    //懒汉加载(双重校验)
    //构造方法私有化
    private Singleton03(){};
    //volatile关键字 只能添加在变量前
    //1,jvm不会重新优化排序
    //2.多线程情况下的可见性使其他线程可见
    private static volatile Singleton03 singleton03;
    //对外提供静态方法获取实例
    public static Singleton03 getInstance(){
        //第一次判断,如果不为空,不进入抢锁阶段,直接返回实例
        if(singleton03==null){
            synchronized (Singleton03.class){
                //抢到锁后再次判空
                if(singleton03==null){
                    singleton03=new Singleton03();
                }
            }
        }
        return singleton03;
    }

}

        双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

        要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

总结:

        添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。

​ 如果是 1-3-2,那么在第 3 步执行完以后,singleton 就不是 null 了,可是这时第 2 步并没有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。假设此时线程 2 进入 getInstance 方法,由于 singleton 已经不是 null 了,所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错,详细流程如下图所示:

还有,这里的private static volatile Singleton singleton = null;中的volatile也必不可少,volatile关键字可以防止jvm指令重排优化,

在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。这里是因为 singleton = new Singleton() ,它并非是一个原子操作,事实上,在 JVM 中上述语句至少做了以下这 3 件事:

第一步是给 singleton 分配内存空间;

第二步开始调用 Singleton 的构造函数等,来初始化 singleton;

第三步,将 singleton 对象指向分配的内存空间(执行完这步 singleton 就不是 null 了)。

 volatile关键字的第二个作用,保证变量在多线程运行时的可见性:

        在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前 的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就 可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数 据的不一致。 要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行 读取。

 

3.单例模式优点和缺点:

优点:单例类只有一个实例,节省了内存资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;单例模式可以在系统设置全局的访问点,优化和共享数据,例如前面说的Web应用的页面计数器就可以用单例模式实现计数值的保存。

servlet:

缺点:单例模式一般没有接口,扩展的话除了修改代码基本上没有其他途径。

4.懒汉模式和饿汉模式区别 面试题

​ 懒汉模式的优点便是在代码中没有使用的情况下,不会去加载单例类的资源不会造成资源的浪费。缺点也很明显,加锁同步会带来程序运行效率的损失。

​ 饿汉模式的优缺点恰好与懒汉模式相反,如果明确知道单例对象在程序代码中用的很频繁,就可以考虑使用饿汉模式了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值