• 设计模式:创建型->单例模式


    创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
     
    以下参考这篇文章:https://zhuanlan.zhihu.com/p/160842212
     

    什么是单例模式

    单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
     

    两种类型

      • 懒汉式:在真正需要使用对象时才去创建该单例类对象
      • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

    其实比较好理解的

    饿汉式

    饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可。我们目前可以简单认为在程序启动时,这个单例对象就已经创建好了

    public class Singleton {
        //饿汉式
        private static Singleton singleton=new Singleton();
        private Singleton() 
        { 
            
        }
        public Singleton getInstance()
        {
            return singleton;
        }
    
    }

    构造方法私有,这样就不能在外部new出对象,只能使用getInstance获取对象。

    优缺点:

    懒汉式

    public class Singleton {
        //懒汉式
        private static Singleton singleton;
        private Singleton()
        {}
        public Singleton getInstance()
        {
            if(singleton==null)
            {
                singleton=new Singleton();
            }
            return singleton;
        }
    }

    最基础的写法,类加载的时候没有实例化,使用时才new

    但这种写法会出现并发问题

    如果两个线程同时判断singleton为空,那么它们都会去实例化一个Singleton对象,这就变成双例了。所以,我们要解决的是线程安全问题。

     加上同步方法或者同步代码块

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
    // 或者
    public static Singleton getInstance() {
        synchronized(Singleton.class) {   
            if (singleton == null) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }

    但这两种方式都不好,我们要锁住的是 创建对象这一过程

    获取对象这个过程没必要加锁

    public static Singleton getInstance() {
        if (singleton == null) {  // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
            synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
                if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    这种写法相对较好:

    • 之所以进入判断后还要再判断一次,是因为可能其他线程已经把它实例化了
    • 锁住的为什么是 Singleton.class 锁住一个类

    指令重排问题

    创建一个对象,在JVM中会经过三步:

    (1)为singleton分配内存空间

    (2)初始化singleton对象

    (3)将singleton指向分配好的内存空间

    指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能

    在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。使用volatile关键字可以防止指令重排序。

    public class Singleton {
        
        private static volatile Singleton singleton;
        
        private Singleton(){}
        
        public static Singleton getInstance() {
            if (singleton == null) {  // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
                synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化
                    if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
        
    }

    最终代码,把对象用volatile修饰

    单例模式使用场景

    创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

     

  • 相关阅读:
    FreeSql 教程引导
    Day3-JS-JavaScript 函数专题
    Day2-JS-JSON
    Day2-JS-let和const
    Day2-JS-this 关键字
    Day2-JS-JavaScript 验证 API
    Day2-JS-表单
    Day2-JS-严格模式
    Day2-JS-JavaScript 错误
    Day2-JS-正则表达式
  • 原文地址:https://www.cnblogs.com/take-it-easy/p/14600004.html
Copyright © 2020-2023  润新知