首页 > 其他分享 >设计模式-单例模式

设计模式-单例模式

时间:2023-01-05 12:45:40浏览次数:51  
标签:Singleton getInstance 模式 instance 线程 单例 static 设计模式 public

 

时间:2023/01/04

 

一. 单例模式介绍

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

实现方式(八种):

1. 饿汉式(静态常量)

2. 饿汉式(静态代码块)

3. 懒汉式(线程不安全)

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

5. 懒汉式(线程不安全,同步代码块)

6. 双重检查

7. 静态内部类

8. 枚举

 

二. 饿汉式(静态常量)

步骤:

1. 构造器私有化

2. 类的内部创建对象

3. 向外暴露一个静态的公共方法

代码:

package singleton;

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{
    // 1. 构造器私有化,外部不能new
    private Singleton(){

    }

    // 2. 本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    // 3. 提供一个公有的静态方法,返回对象实例
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点说明:

1. 优点:这种写法比较简单,就是在类加载的时候就完成了实例化。避免了线程同步问题。

2. 缺点:在类加载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

3. 这种基于类加载的机制避免了多线程同步问题。但是在类加载时就进行实例化会存在很多问题,在单例模式中是通过调用getInstance方法来实现类加载,这种导致类加载的方式是我们希望看到的,因为我们调用getInstance方法就说明我们会用到这个单例对象,但是在实际中导致类加载的原因会有很多,如果不是通过调用getInstance方法导致类加载,就会出现我们不想使用单例对象,但是该对象却已经存在内存的情况,这会浪费内存空间,没有达到Lazy Loading的效果。

4. 结论:这种单例模式可用,但是可能造成内存浪费。

 

三. 饿汉式(静态代码块)

步骤:

1. 构造器私有化

2. 声明一个该类的对象

3. 在静态代码块中创建单例对象

4. 提供一个公有的静态方法,返回对象实例

package singleton;

public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton02{
    // 1. 构造器私有化,外部不能new
    private Singleton02(){

    }

    // 2. 声明一个该类的对象
    private static Singleton02 instance;

    // 3. 在静态代码块中创建单例对象
    static {
        instance = new Singleton02();
    }

    // 4. 提供一个公有的静态方法,返回对象实例
    public static Singleton02 getInstance(){
        return instance;
    }
}

优缺点说明:

1. 与静态常量方法类似,优缺点和上面一样。

2. 结论: 这种单例模式可用,但是可能造成内存浪费。

 

四. 懒汉式(线程不安全)

步骤:

1. 声明一个该类的静态私有对象

2. 构造器私有化

3. 提供一个静态的公有方法,当使用到该方法时,采取创建该类的对象

package singleton.type3;

public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    // 提供一个静态的公有方法,当使用到该方法时,才去创建instance(懒汉式)
    public static Singleton getInstance(){
        if(instance == null)
            instance = new Singleton();
        return instance;
    }
}

优缺点说明:

1. 起到了Lazy Loading的效果,但是只能在单线程下使用。

2. 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式(因为会违反单例模式的初衷)。

3. 结论:在实际开发中,不要使用这种方式。

 

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

步骤:

1. 声明一个该类的静态私有对象

2. 构造器私有化

3. 提供一个静态的公有方法,加入同步处理代码,解决线程安全问题

package singleton.type4;

public class SingletonTest04 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    // 提供一个静态的公有方法,加入同步处理的代码(synchronized),解决线程安全问题
    public static synchronized Singleton getInstance(){
        if(instance == null)
            instance = new Singleton();
        return instance;
    }
}

优缺点说明:

1. 解决了线程不安全问题。

2. 效率太低了,每个线程在想获取类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。(每次调用getInstance()方法都需要同步,实际上真正实例化的只有一次)

3. 结论:在实际开发中,不推荐使用这种方式。

 

六. 懒汉式(线程不安全,同步代码块)

1. 声明一个该类的静态私有对象

2. 构造器私有化

3. 提供一个静态的公有方法,加入同步处理块,但是无法解决线程安全问题

package singleton.type5;

public class SingletonTest05 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    // 提供一个静态的公有方法
    public static synchronized Singleton getInstance(){
        // 同步代码块
        if(instance == null){
            synchronized (Singleton.class){
                instance = new Singleton();
            }
        }

        return instance;
    }
}

优缺点说明:

1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方式效率太低,改为同步产生实例化的代码块。

2. 但是这种同步并不能起到线程同步的作用。加入一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

3. 结论:在实际开发中,不能使用这种方式。

 

七. 双重检查

volatile关键字:一种轻量级的同步机制,保证可见性,不保证原子性,并且禁止指令重排。

具体可见参考:https://blog.csdn.net/u012723673/article/details/80682208

代码如下:

package singleton.type6;

public class SingletonTest06 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    // volatile: 一种轻量级的同步机制,保证可见性,不保证原子性,并且禁止指令重排。
    private static volatile Singleton instance;

    private Singleton(){}

    // 提供一个公有静态方法
    public static synchronized Singleton getInstance(){

        // 第一次检查
        if(instance == null){
            // 同步代码块
            synchronized (Singleton.class){
                // 第二次检查
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

双重检查的思想:假设有三个线程a、b、c,如果线程a和b已经通过了第一次检查,但是都还没有进入同步代码块,而线程c还没有进入第一次检查,此时如果线程a先进入同步代码块,则线程b不能进入,然后a通过第二次检查并创建对象,之后a会退出同步代码块,此时b就可以进入到同步代码块中,但是由于instance对象已经被创建,b就不会创建新的对象,保证了内存中只有一个对象,然后b退出同步代码块。对于线程c,它此时执行第一次检查,由于instance不为null,所以不会执行到同步代码块,这样会大大提高效率,解决了懒汉式(同步方法)存在的问题。

优缺点说明:

1. Double-Check(双重检查)概念是多线程开发中常使用到的,如上述代码中所示,我们进行了两次if(singleton == null)检查,这样就可以保证线程安全。

2. 这样,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null), 直接return实例化对象,也避免了反复进行方法同步。

3. 线程安全,延迟加载,效率较高。

4. 结论:在实际开发中,推荐使用这种单例设计模式。

 

八. 静态内部类

代码如下:

package singleton.type7;

public class SingletonTest07 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    // 1. 构造器私有化
    private Singleton(){}

    // 2. 写一个静态内部类,该类中有一个静态属性instance
    private static class SingletonInstance{
        private static final Singleton instnace = new Singleton();
    }

    // 3. 提供一个静态公有方法
    public static Singleton getInstance(){
        return SingletonInstance.instnace;
    }

}

静态内部类方法利用了静态内部类的两个特点:

1. 当外部类被加载(装载)时,静态内部类不会被加载,这实现了Lazy Loading。

2. 当调用到了getInstance()方法时,静态内部类会被加载,该类只会被加载一次,而且类加载是线程安全的,所以不会有安全问题。

优缺点说明:

1. 这种方式采用了类加载的机制来保证初始化实例时只有一个线程。

2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装在SingletonInstance类,从而完成instance的实例化。

3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的的。

4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

5. 结论:推荐使用。

 

九. 枚举

代码如下:

package singleton.type8;

public class SingletonTest08 {

    public static void main(String[] args) {
        Singleton instance = Singleton.instance;
        Singleton instance1 = Singleton.instance;

        System.out.println(instance == instance1);  // true

        instance.sayOK();   // ok~
    }
}

enum Singleton{
    instance;   // 属性

    public void sayOK(){
        System.out.println("ok~");
    }
}

优缺点说明:

1. 借助JDK 1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

2. 这种方式是Effective Java作者Josh Bloch提倡的方式。

3. 结论:推荐使用。

 

十. 单例模式注意事项和细节说明

注意事项和细节说明:

1. 单例模式保证了系统内存中只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。

3. 单例模式使用的场景:需要频繁的进行创建和销毁对象、创建对象耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

 

标签:Singleton,getInstance,模式,instance,线程,单例,static,设计模式,public
From: https://www.cnblogs.com/machi12/p/17026170.html

相关文章

  • 三种异步模式(扫盲)&BackgroundWorker
    1.APM&EAP&TAP.NET支持三种异步编程模式分别为APM、EAP和TAP:1.基于事件的异步编程设计模式 (EAP,Event-basedAsynchronousPattern)EAP的编程模式的代码命名有以下特点: ......
  • 低代码开发——创新赋能企业办公模式转变
    近年来,国内云计算高速发展、SAAS软件模式快速普及、企业数字化的需求增长等方面促进了低代码平台开发与应用的高速发展,使之成为继RPA之后企业数字化转型的明星工具。如果......
  • 设计模式简单介绍
    注:所有知识来源于《设计模式:可复用软件面向对象的基础》1什么是设计模式ChristopherAlexander说过:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解......
  • STM32用寄存器将输入模式配置成上拉或者下拉模式的方式
    我注意到配置CRH/CRL寄存器时写入10会配置成上拉/下拉输入模式,于是去网上搜罗了一下,寻找寄存器中是如何配置成这两种不同的输入方式的。结果:CRH/CRL寄存器写1默认是下拉......
  • CSS - 块元素,行内元素,行内块元素与显示模式的转换
    1.块元素div,p,h1,ol,ul,dl,tabel,form块元素的特点:1.独占一行2.宽度高度内外边距都可以控制3.宽度默认是容器(父级容器)的100%4.里面可以放块元素,行内元素,行内块元素5......
  • 对于goland相对较新一些版本新建项目时没用go mod模式选项的坑
    前言对于一些小白在网上看很早的一些go视频,使用goland2020.3.x版本或者其之前版本创建新项目,里面会有GOModules(vgo)这个选项,也就是gomod模式创建新项目,然而对于现在相对新......
  • 工厂模式
    设计模式六大原则:开放封闭原则对扩展开放,对修改关闭,在增加新功能的时候,能不改代码尽量不修改。单一职责原则里氏替换原则调用父类方法可以执行,调用子类方法也应该完......
  • unity3d,android平台下,高德地图定位,定位模式设定
    接上一篇,有时候不是想偷懒,实在是不会用androidstudio再次打包啊。高德地图定位androidsdk中,设置定位模式,java代码如下://设置定位模式为高精度模式,Battery_Saving为低功耗......
  • 工厂模式C++实现 (内附简单源码实现)
    抽象工厂模式为什么要用抽象工厂模式?*举个实际应用的例子,一个显示器电路板厂商,旗下的显示器电路板种类有非液晶的和液晶的;这个时候,厂商建造两个工厂,工厂A负责生产非......
  • 面试官:Docker 有几种网络模式?5 年工作经验都表示答不上来。。
    本文作者:知知行行本文链接:https://www.cnblogs.com/loronoa/p/16566818.htmldocker容器网络Docker在安装后自动提供3种网络,可以使用dockernetworkls命令查看[root@l......