当前位置: 首页 > article >正文

设计模式(二)-创建者模式(1)-单例模式

概述

为何需要单例模式(Singleton Pattern)?

在程序运行当中,我们只希望一个类只能创建一个对象,在多个地方可以公用这个唯一的对象。

特点: 必须保证类只能创建一个对象。

单例模式可分为:饿汉式和懒汉式

1)饿汉式
特点: 类加载时(程序一开始运行时),该单例对象就被创建。
(所谓饿,饿肚子就马上干饭,所以程序一运行,就会被创建。)
优点: 没有出现线程安全问题。
缺点: 浪费内存空间。如果创建一个非常大的实例,到程序结束都没有被使用到,就浪费了资源。同时也会导致程序运行很慢,尤其在程序启动的时候。

2)懒汉式:
特点: 类加载不会创建单例对象,而是在程序运行中首次使用到单例对象时,才会开始被创建。
(所谓懒,不需要时不会创建。需要时才去检查有没有实例化,如果有就会返回实例对象,没有则会创建一个。)
缺点: 线程不安全。在程序运行中,可能会存在多个线程轮询干活。如果有多个线程同时判断是否有单例对象,如果都判断没有,就同时创建了多个对象。

1、饿汉式(静态变量)
    public class Singleton
    {
        private Singleton() { }
        private static Singleton instance = new Singleton();
        public static Singleton getInstance(){ return instance;}
    }
2、饿汉式(静态代码块)
//通过静态代码块(C# 不支持)
    public class Singleton
    {
        private Singleton() { }
        private static Singleton instance;
        static { instance = new Singleton();
        public static Singleton geetInstance() { return instance; }
    }
3、懒汉式(线程不安全)
public class Singleeton
    {
        private Singleeton() { }
        private static Singleeton instance;
        public static Singleeton getInstance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Singleeton();
                }
                return instance;
            }
        }

缺点: 如果有多个线程同时访问单例对象,会可能存在上一个线程还没有创建好对象时,而下一个线程因为判断该对象为空就重复创建一个对象。

4、懒汉式(线程安全)
   public class Singleeton
    {
        private Singleeton() { }
        private static Singleeton instance;
        private static object lockobj = new object();

        public static Singleeton getInstance()
        {
        //加锁的作用:保证创建唯一的单例对象
        //缺点:当单例对象创建好后,仍会导致其他线程进行等待抢锁,而不是直接返回该对象。
            lock(lockobj)
            {
                if (instance == null)
                {
                    instance = new Singleeton();
                }
            }
            return instance;
        }
    }

优点: 解决了多个线程重复创建对象的问题。因为在单例对象判断是否存在前加了把锁,说明需要等待上一个线程创建好单例对象了,才能把执行权分配给下一个线程去执行 if 代码块。

缺点: 影响性能。如果单例对象已经创建好,其他线程为了使用该单例对象时,仍然会进行排队抢锁,从而增加了抢锁的时间,而不是直接返回该对象。

5、懒汉式(双重检查锁)
    public class Singleeton
    {
        private Singleeton() { }
        private static Singleeton instance;
        private static object lockobj = new object();

        public static Singleeton getInstance()
        {
        //如果存在单例对象就直接返回该对象,避免浪费排队强锁的时间。
            if (instance == null)
            {
                lock (lockobj)
                {
                    if (instance == null)
                    {
                        instance = new Singleeton();
                    }
                }
            }

            return instance;
        }
    }

还存在缺点: 在代码执行过程中,会存在指令的执行顺序问题。即指令的执行顺序并不一定会按照我们编写的顺序执行。也就说,系统可能会存在对指令重排序问题。所以这会导致程序不一定能及时拿到单例对象变量(instance)的最新值。这样即使能拿到不为空的 instance 变量,也不确保这个变量是否完全被创建好。

6、懒汉式(双重检查锁改进)
 public class Singleeton
    {
        private Singleeton() { }
        //改进:在双重检查锁的代码中,只在 instance 变量中加了修饰符 volatile.
        private static volatile Singleeton instance;
        private static object lockobj = new object();
        //....
    }

为何 instance 变量 需要使用 volatile 修饰符?

如果 instance 变量去掉修饰符 volatile 的话,就不能保证该代码执行的正确性。因为instance = new SIngleton();这行代码并不是原子操作。(原子操作:不会被线程调度机制打断的操作,中间的执行不会切换到另一个线程来操作,也就是new 单例变量和单例对象具体创建的过程并不是由同一个线程执行的)。

打个比方:骑手送外卖。他一般工作的顺序就是送餐到达地点后,然后联系顾客,最后把餐丢到顾客嘴里。但是有时候呢,他可能因为其他事情如赶时间等原因,还没到送达点,就先联系顾客说外卖到了。结果就是顾客开门没看到外卖,就一直张着嘴,结果吃的是西北风。
如果顾客在APP上设置了通知功能,当骑手真的到送达点了就马上通知顾客。

这个例子,就相当于:

  • 多线程:顾客拿餐、骑手送外卖过程、外卖中途有其他事情等。
  • instance = new instance():顾客用餐。
  • 创建Instance:骑手送餐、联系顾客,送达这一过程。
  • 指令重排序:骑手中途先联系顾客,再到送达点。
  • volatile:一旦骑手到了送达点,APP就会马上通知顾客。否则,就继续等待用餐。

初始化对象的顺序问题
Java 有JVM,同样的C#也会存在相似的运行环境,即 CLR。
在运行环境中,对类对象创建时会存在三个阶段执行:

  • 1)为变量分配内存
  • 2)初始化变量
  • 3)将变量指向分配的内存空间

如果系统存在重排序,就可能会出现执行顺序问题。也就是说,系统可能存在先执行第三步后执行第二步,也可能会正常按顺序执行。前者的执行顺序,会导致变量还没有初始化完成,其他线程就已经判断了该变量值不为空,然后返回一个没有初始化完成的单例对象。

volatile的作用
(1)保证并发编程的可见性(不保证原子性)
单线程时重排序无影响,但是多线程就有可能会读取到脏数据,这就需要用 volatile。使用 volatile 修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新值。

(2)禁止指令重排序
用 volatile 修饰保证执行顺序。


http://www.kler.cn/a/135645.html

相关文章:

  • linux安装netstat命令
  • Android S长按文件或视频或编辑中文字或输入框中文字不会弹出分享菜单
  • 动态规划 —— dp 问题-买卖股票的最佳时机IV
  • 【GVN】AWZ算法
  • Springboot应用的端口配置方法解析与优先级详解
  • 深 度 学 习
  • uniapp Android如何授权打开系统蓝牙Bluetooth?
  • 【ES6标准入门】JavaScript中的模块Module语法的使用细节:export命令和imprt命令详细使用,超级详细!!!
  • vue统一登录
  • C# 依赖注入如何实现
  • 数据结构【DS】栈
  • 音视频项目—基于FFmpeg和SDL的音视频播放器解析(十三)
  • 二维偏序问题
  • 从傅里叶变换,到短时傅里叶变换,再到小波分析(CWT),看这一篇就够了(附MATLAB傻瓜式实现代码)
  • 多维时序 | MATLAB实现PSO-BiGRU-Attention粒子群优化双向门控循环单元融合注意力机制的多变量时间序列预测
  • 毕业设计ASP.NET 2368酒店信息管理系统【程序源码+文档+调试运行】
  • 腐蚀监测常用技术及作用
  • 解决word之间复制公式时,公式编辑器变成图片
  • 【算法】堆排序
  • (BMS)电池管理系统技术研究与仿真
  • Selenium安装WebDriver最新Chrome驱动(含116/117/118/119)
  • 为什么阿里推荐 LongAdder ,不推荐 AtomicLong ??
  • 蓝桥杯每日一题2023.11.20
  • WPF Visual, UIElement, FrameworkElement, Control这些类的区别
  • 【论文阅读】基于隐蔽带宽的汽车控制网络鲁棒认证(二)
  • 力扣刷题-二叉树-二叉树最小深度