单例模式

《单例模式》

〇、概述

说明

演示语言:Java。

演示工具:IDEA。

参考视频:

https://www.bilibili.com/video/BV1gJ411X7uN?p=29&t=4.9

什么是单例模式

所谓类的单例设计模式,就是采用一定的方式保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得取对象实例的方法(静态方法)。

比如 HibernateSessionFactory ,它充当数据存储源的代理,并负责创建 Session 对象SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这就会使用到单例模式。

推荐使用的

饿汉式【可以用,但是可能浪费一些资源】

双重检查懒汉式

静态内部类懒汉式

枚举方式。

一、静态常量 饿汉式【√】

1.1 步骤如下:

  1. 构造器私有化。(防止通过new得到对象)
  2. 类的内部创建对象。
  3. 向外暴露一个静态的公共方法。 getInstance 返回该类的静态实例。

1.2 代码实现如下:

/*
* 静态常量 饿汉式
*/
class SingletonStaticConst {
    //1.构造器私有化。
    private SingletonStaticConst(){}
    //2.类的内部创建对象。
    private final static SingletonStaticConst instance = new SingletonStaticConst();
    //3.向外暴露一个静态的方法以返回该类的静态实例。
    public static SingletonStaticConst getInstance(){
        return instance;
    }
}
/*
* 测试类。多次拿到的实例对象是同一个。
*/
class Test(){
    SingletonStaticConst instance1 = SingletonStaticConst.getInstance();
    SingletonStaticConst instance2 = SingletonStaticConst.getInstance();
    System.out.println(instance1.hashCode() == instance2.hashCode() );   //true
}

1.3 优缺点:

静态常量饿汉式的优缺点:

  1. 优点:这种写法比较简单。就是在类装载的时候就完成实例化。避免了线程同步问题。
  2. 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 懒加载 的效果。如果从始至终都没有使用过这个实例,则会造成内存的浪费。
  3. 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类的装载时就实例化,在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(获取其他的静态方法)倒置类装载,这时候初始化 instance 就没有达到 Lazy Loading 的效果。
  4. 这种单例模式可以用,但是可能造成内存浪费。

二、静态代码块饿汉式【√】

将第二步,在类的内部创建对象,修改为代码块。

/**
* 静态代码块饿汉式
*/
class SingletonStaticCode{
    //1.构造器私有化
    private SingletonStaticCode(){}
    //2.静态变量在代码块里边创建。
    private static SingletonStaticCode instance;
    static{
        instance = new SingletonStaticCode();
    }
    //3.对外提供方法。
    public static SingletonStaticCode getInstance(){
        return instance;
    }
}

这种方式和上面的方式其实类似,只不过将类的实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

三、线程不安全懒汉式【x】

thread-unsafe 线程不安全 ;

Lazy 懒汉

线程不安全的懒汉式单例模式 SingletonUnsafeLazy

代码示例:

/**
* 线程不安全的懒汉式单例模式
*/
class SingletonUnsafeLazy{
    //构造器私有化
    private SingletonUnsafeLazy(){}
    
    private static SingletonUnsafeLazy instance;
    
    //提供一个静态的对外的方法,当使用到该方法时,采取创建对象。
    public static SingletonUnsafeLazy getInstance(){
        if(instance == null){
            instance = new SingletonUnsafeLazy();
        }
        return instance;
    }
}

优缺点:

  1. 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了 if(instance == null) 判断语句里边,还未来得及往下执行,另一个线程也通过了这个判断语句,这是便会产生多个实例。所以在多线程环境下不可使用这种方式。
  3. 结论:在实际开发中,不要使用这种方式。

四、线程安全 懒汉式【x】

线程安全的懒汉式单例模式 SingletonSafeLazy

/**
* 线程安全的懒汉式。在对外提供的方法中加 锁
*/
class SingletonSafeLazy{
    private SingletonSafeLazy();
    private static SingletonSafeLazy instance;
    
    public static synchronized SingletonSafeLazy getInstance(){
        if(instance == null){
            instance = new SingletonSafeLazy();
        }
        return instance;
    }
}

优缺点:

  1. 解决了线程不安全的问题。
  2. 效率低下。每个线程在想要获得类的实例时,执行 getInstance() 方法都要进行同步。而其实这个方法执行一次实例化代码就够了,后面的想获得该类的实例,直接 return 就行了。方法同步效率太低。

五、同步代码块懒汉式【x】

同步代码块懒汉式单例模式 SingletonSynCode

/**
* 同步代码块的懒汉式单例模式
*/
class SingletonSynCode{
    private SingletonSynCode(){}
    private static SingletonSynCode singleton;
    
    public static SingletonSynCode getInstance(){
        if(singleton == null){
            synchronized(SingletonSynCode.class){
                singleton = new SingletonSynCode();
            }
        }
        return singleton;
    }
}

优缺点:

  1. 这种方式,本意是对第四种方式的改进,因为前面的同步方法效率太低,改为同步产生实例化的代码块。
  2. 但是这种方式并不能起到线程同步的作用。跟第三种方式遇到的情形一样,加入一个线程进入了 if判断语句块,还没有来得及往下执行,另一个线程也通过了这个判断语句,这是会产生多个实例。
  3. 结论:在实际开发中,不能用这种方式

六、双重检查【√】

双重检查 double check

代码示例:

/**
* 双重检查懒汉式
*/
class SingletonDoubleCheck{
    private SingletonDoubleCheck(){}
    private static volatile SingletonDoubleCheck singleton;
    
    public static SingletonDoubleCheck getInstance(){
        if(singleton == null){
            synchronized(SingletonDoubleCheck.class){
                if(singleton == null){
                    singleton = new SingletonDoubleCheck();
                }
            }
        }
        return singleton;
    }
}

解决了懒加载问题了,解决了线程安全问题,同时保证了效率。

优缺点:

  1. 双重检查概念是多线程开发中常使用的。进行了两次 if(singleton == null)这样就可以保证线程安全。
  2. 实例化代码只用执行一次。后面再次访问时,避免了反复进行方法同步。
  3. 线程安全:延迟加载,效率较高。
  4. 结论:在实际开发中,推荐使用这种单例设计模式。

七、静态内部类【√】

static inner classes

SingletonStaticInnerClasses

代码示例

/**
* 静态内部类的单例模式,利用了jvm机在装载类的时候是线程安全的这一特点,保证了外部类的实例化的单例的线程安全的。
*/
class SingletonStaticInnerClasses{
    private SingletonStaticInnerClasses(){}
    
    private statci class SingletonInstance{
        private static final SingletonStaticInnerClasses INSTANCE = new SingletonStaticInnerClasses();
    }
    
    public static SingletonStaticInnerClasses getInstance(){
        return SingletonInstance.INSTANCE;
    }
}
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在外部类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance()方法,才会装载内部类,从而完成外部类的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化。所以在这里,jvm帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

八、枚举方式【√】

代码示例:

enum SingletonEnum{
    INSTANCE;
    public void method(){
        //...
    }
}
  1. 借助JDK1.5 中新增的枚举来实现单例模式,不仅能够避免多线程同步的问题。而且还能防止反序列化重新创建新的对象。

九、单例模式在JDK应用的源码分析

java.lang.Runtime 就是经典的单例模式。使用了饿汉式。

public class Runtime{
    private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime(){
        return currentRuntime;
    }
    private Runtime(){}
}

十、单例模式注意事项

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