设计模式之单例模式

目录

单例模式大致介绍

单例模式之饿汉式

静态变量

 静态代码块

单例模式之懒汉式

1)懒汉式(线程不安全)

2) 懒汉式(线程安全,同步方法)

3) 懒汉式(线程安全,同步代码块)

4) 双重检查

5) 静态内部类

6) 枚举

源码中探究单例模式

1)JDK中

 2)Spring框架中

总结:


单例模式大致介绍

相信接触java的小伙伴都对单例模式有一点了解。

单例模式属于创建型模式,其主要为:在一个系统或一个项目中,我们对于某一类对象只创建保存维护一个实例对象

在我们常用的单例模式主要分为两种,一种是饿汉式,一种是懒汉式

饿汉式与懒汉式的主要区别为:饿汉式是对象随着类的加载而创建,即一开始便创建好了对象,而懒汉式是指在你调用时才会创建对象

由其区别,可以分析:

饿汉式的主要优点为:实例是随着类的加载而加载的,故此避免了线程同步问题,而且较为简单;

缺点也很明显:如果没用到懒汉式的这个实例,就会造成内存的浪费。

而懒汉式的优点为:能够充分利用,在调用时才创建。

缺点:就是容易造成线程同步,造成线程不安全问题。

单例模式细分下来,可达8种之多!

饿汉式分为2种,懒汉式分为6种!

单例模式之饿汉式

首先我们对于饿汉式的两种先做介绍。

饿汉式分为静态变量静态代码块两种。

静态变量顾名思义,就是将对象作为类的静态变量,通过显式赋值的方式,来对对象进行初始化。

静态代码块则是通过静态代码块的形式进行赋值(即创建对象)。

由于不是很难,我便写上代码。

静态变量

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 10:26
 */
//饿汉式1:静态变量
public class Singleton1 {
    public static void main(String[] args) {
        //验证其存在的对象实例是否唯一
        Singleton1 singleton1 = Singleton1.getSingleton1();
        Singleton1 singleton2 = Singleton1.getSingleton1();
        System.out.println(singleton1==singleton2);//true
    }
    //此时的对象是在类的变量中显式创建的
    private static Singleton1 singleton1= new Singleton1();
    //构造器私有是为了防止外界通过new+构造器的方式创建对象
    private Singleton1(){}
    //我们通过对外暴露一个public的方法,供外界获取实例对象
    //这里也符合设计模式原则之迪米特法则,即最小知道原则,尽可能提供少的public方法给外部,把其它不必要的进行隐秘封装
    public static Singleton1 getSingleton1(){
        return singleton1;
    }
}

 静态代码块

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 10:30
 */
//饿汉式1:静态代码块
public class Singleton2 {
    public static void main(String[] args) {
        //验证其存在的对象实例是否唯一
        Singleton2 singleton1 = Singleton2.getSingleton2();
        Singleton2 singleton2 = Singleton2.getSingleton2();
        System.out.println(singleton1==singleton2);//true
    }
    //定义变量,此时不显式赋值
    private static Singleton2 singleton2;
    static {//在静态代码块中创建对象
        singleton2 = new Singleton2();
    }
    //构造器私有是为了防止外界通过new+构造器的方式创建对象
    private Singleton2(){}
    //我们通过对外暴露一个public的方法,供外界获取实例对象
    public static Singleton2 getSingleton2(){
        return singleton2;
    }
}

单例模式之懒汉式

懒汉式共分为六种:

1) 懒汉式(线程不安全)
2) 懒汉式(线程安全,同步方法)
3) 懒汉式(线程安全,同步代码块)
4) 双重检查
5) 静态内部类
6) 枚举

我们通过在代码中探究:

1)懒汉式(线程不安全)

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 10:37
 */
//懒汉式(线程不安全)
public class Singleton3 {
    public static void main(String[] args) {
        /*
        分析问题:
        对于本类中的getSingleton3方法,如果多个线程同时进入该方法的if判断,初始大家都会判断singleton3为null
        会导致每个线程都会new一个对象
        因为无法保证该系统中只存在一个实例,故此存在线程不安全的问题
        但是如果是单线程通过,则不存在线程不安全的问题
         */
        System.out.println("=====单线程下====");
        Singleton3 singleton1 = Singleton3.getSingleton3();
        Singleton3 singleton2 = Singleton3.getSingleton3();
        System.out.println(singleton1 == singleton2);
    }

    //创建一个属性变量
    private static Singleton3 singleton3;

    //私有构造器,防止外界new对象
    private Singleton3() {
    }

    //我们通过对外暴露一个public的方法,供外界获取实例对象
    public static Singleton3 getSingleton3() {
        //保证只有一个实例
        if (singleton3 == null) {
            singleton3 = new Singleton3();
        }
        return singleton3;
    }
}

2) 懒汉式(线程安全,同步方法)

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 10:51
 */
/*
对上一种方法的问题分析:
对于本类中的getSingleton3方法,如果多个线程同时进入该方法的if判断,初始大家都会判断singleton3为null
会导致每个线程都会new一个对象
因为无法保证该系统中只存在一个实例,故此存在线程不安全的问题
但是如果是单线程通过,则不存在线程不安全的问题
*/
//懒汉式(线程安全,同步方法)
public class Singleton4{
    public static void main(String[] args) {
        /*
        对于本种方式的问题分析:
        效率太低,每个线程进入getSingleton4方法时,都需要保持同步,在线程外等待
        其实我们并不需要等待,假设发现线程进入了,我们只需要判断singleton4不为空时返回就行了
        因为一旦有线程进入了,我们就知道,有线程在实例化,或者已经实例化完了
        如果实例化完了,后续的线程仍在等待,这样效率是很低了,直接返回对象就可以了
         */
    }
    //创建一个属性变量
    private static Singleton4 singleton4;

    //私有构造器,防止外界new对象
    private Singleton4() {
    }

    //我们通过对外暴露一个public的方法,供外界获取实例对象
    //我们把该方法设置为同步方法,此时即能够避免线程不安全问题
    public static synchronized Singleton4 getSingleton4() {
        //保证只有一个实例
        if (singleton4 == null) {
                singleton4 = new Singleton4();
        }
        return singleton4;
    }
}

3) 懒汉式(线程安全,同步代码块)

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 11:21
 */
//懒汉式(线程安全,同步代码块)
public class Singleton5 {
    /*
    对上一种方法的问题分析:
    效率太低,每个线程进入getSingleton4方法时,都需要保持同步,在线程外等待
    其实我们并不需要等待,假设发现线程进入了,我们只需要判断singleton4不为空时返回就行了
    因为一旦有线程进入了,我们就知道,有线程在实例化,或者已经实例化完了
    如果实例化完了,后续的线程仍在等待,这样效率是很低了,直接返回对象就可以了
    */
    public static void main(String[] args) {
        /*
        对于本次改进的问题分析:
        初始多线程一起进入方法,singleton5的初始值为null,都进了if的判断里面
        一个线程握住锁,其余线程都在外面等待
        当握住线程的锁创建实例完毕之后,换下一个线程握锁,又重新实例化,
        这就导致了线程不安全的问题,故此该方法看似线程安全,实则线程不安全
         */
    }

    //创建一个属性变量
    private static Singleton5 singleton5;

    //私有构造器,防止外界new对象
    private Singleton5() {
    }

    //我们通过对外暴露一个public的方法,供外界获取实例对象
    public static  Singleton5 getSingleton5() {
        //保证只有一个实例
        if (singleton5 == null) {
            //握住类锁
            synchronized (Singleton5.class) {
                singleton5 = new Singleton5();
            }
        }
        return singleton5;
    }
}

4) 双重检查

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 11:32
 */
//双重检查
public class Singleton6 {
    /*
   对上一种方法的问题分析:
   初始多线程一起进入方法,singleton5的初始值为null,都进了if的判断里面
   一个线程握住锁,其余线程都在外面等待
   当握住线程的锁创建实例完毕之后,换下一个线程握锁,又重新实例化,
   这就导致了线程不安全的问题,故此该方法看似线程安全,实则线程不安全
   */
    //创建一个属性变量
    //volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的对象
    private static volatile Singleton6 singleton6;

    //私有构造器,防止外界new对象
    private Singleton6() {
    }

    //我们通过对外暴露一个public的方法,供外界获取实例对象
    /*
    解决方案:
    对属性增添一个关键字volatile
    我们采取双重检查的方式
    握住锁之后,再进行一次空判断,即可达成目的
     */
    public static Singleton6 getSingleton6() {
        //保证只有一个实例
        if (singleton6 == null) {
            //握住类锁
            synchronized (Singleton6.class) {
                if (singleton6 == null) {//在锁里面同样进行一次空判断
                    singleton6 = new Singleton6();
                }
            }
        }
        return singleton6;
    }
}

5) 静态内部类

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 11:40
 */
//静态内部类
public class Singleton7 {
    //创建一个属性变量
    private static volatile Singleton7 singleton7;

    public static void main(String[] args) {
        Singleton7 singleton7 = Singleton7.getSingleton7();
        Singleton7 singleton71 = Singleton7.getSingleton7();
        System.out.println(singleton7==singleton71);//true
        /*
        本方法的原理:
        我们知道,jvm在类加载中,会将其static修饰的变量同样进行加载,
        在Singleton7这个类中,SingletonInstance作为其静态内部类,同样也会被加载
        而SingletonInstance中的属性也会被加载,
        也就是说,SingletonInstance中的属性是单例模式的对象,在类加载阶段就会被初始化,与(1)(2)方法同理
        且因为jvm在加载类中,类只会被加载一次,这也就不存在线程安全问题
        这也就保证了一个实例的情况
         */
    }
    //私有构造器,防止外界new对象
    private Singleton7() {
    }
    //创建一个静态内部类
    static class SingletonInstance{
        //定义一个属性,属性值为单例模式的对象
        //需要加final关键字,防止其值被修改
        private static final Singleton7 INSTANCE  = new Singleton7();
    }
    //我们通过对外暴露一个public的方法,供外界获取实例对象
    public static Singleton7 getSingleton7() {
        return SingletonInstance.INSTANCE;
    }
}

6) 枚举

package singleton;

/**
 * @author liuweixin
 * @create 2022-01-25 11:50
 */
//枚举
public class Singleton8 {
    public static void main(String[] args) {
        /*
        本方法的思想:
        我们通过jdk1.5引入的enum枚举关键字,通过枚举方式实现单例模式
         */
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;
        System.out.println(instance==instance1);//true
    }
    enum Singleton {
        INSTANCE; //属性
    }
}

源码中探究单例模式

1)JDK中

我们可以看到在JDK源码中的Runtime类中就有使用到单例模式的身影,如图即采用了饿汉式的单例模式

 2)Spring框架中

用过spring框架的小伙伴应该都知道,我们在spring的配置文件创建bean对象时,会有这么个配置

 标签中的scope属性,便是指明,通过单例模式来创建该对象。该属性默认值为Singleton,即为单例模式.

通过追溯源码,我们可以看到,在AbstractBeanFactory类中的getBean()方法中调用了getSingleton()方法,即为获取单例对象。

 我们追溯getSingleton()方法,发现其在DefaultSingletonBeanRegistry类中实现,通过观察其实现代码,我们可以得知,spring中的单例模式的获取采用的是上述懒汉式的(4)双重检查的方式。

 再追溯一下,发现getSingleton()方法是定义在一个接口SingletonBeanRegistry()中,而DefaultSingletonBeanRegistry则是实现了该接口,并重写实现了getSingleton()方法,至此,我们可以画出spring中单例模式对应的UML类图:

总结:

饿汉式与懒汉式各有千秋,需要根据具体业务需求采取对应合适的方式,对于饿汉式两种方式都可以使用,对于懒汉式,推荐使用后三种,即4) 双重检查、5) 静态内部类、6) 枚举

注意事项:

1) 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2) 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new
3) 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值