登录  /  注册
博主信息
博文 1
粉丝 0
评论 0
访问量 219
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
一个单例模式,没必要这么卷吧
P粉768608682
原创
219人浏览过

一个单例模式,没必要这么卷吧 

老猫的设计模式专栏已经偷偷发车了。不甘愿做crud boy?看了好几遍的设计模式还记不住?那就不要刻意记了,跟上老猫的步伐,在一个个有趣的职场故事中领悟设计模式的精髓。还等什么?赶紧上车吧

如果把系统软件比喻成江湖的话,那么设计原则绝对是OO程序员的武功心法,而设计模式绝对是招式。光知道心法是没有用的,还是得配合招式。只有心法招式合二为一,遇到强敌(“坑爹系统”)才能见招拆招

故事

之前让小猫梳理的业务流程以及代码流程基本已经梳理完毕【系统梳理大法&代码梳理大法】。从代码侧而言也搞清楚了系统臃肿的原因【违背设计原则】。小猫逐渐步入正轨,他决定从一些简单的业务场景入手,开始着手优化系统代码。那么什么样的业务代码,动了之后影响最小呢?小猫看了看,打算就从泛滥创建的线程池着手吧,他打算用单例模式做一次重构。

在小猫接手的系统中,线程池的创建基本是想在哪个类用多线程就在那个类中直接创建。所以基本上很多service服务类中都有创建线程池的影子。

写在前面

遇到上述小猫的这种情况,我们的思路是采用单例模式进行提取公共线程池执行器,然后根据不同的业务类型使用工厂模式进行分类管理。

接下来,我们就单例模式开始吧。

单例模式定义

单例模式(Singleton)又叫单态模式,它出现目的是为了保证一个类在系统中只有一个实例,并提供一个访问它的全局访问点。从这点可以看出,单例模式的出现是为了可以保证系统中一个类只有一个实例而且该实例又易于外界访问,从而方便对实例个数的控制并节约系统资源而出现的解决方案。

饿汉式单例模式

什么叫做饿汉式单例?为了方便记忆,老猫是这么理解的,饿汉给人的形象就是有食物就迫不及待地去吃的形象。那么饿汉式单例模式的形象也就是当类创建的时候就迫不及待地去创建单例对象,这种单例模式是绝对线程安全的,因为这种模式在尚未产生线程之前就已经创建了单例。

我们看一下上述案例的优缺点:

优点:线程安全,类加载时完成初始化,获取对象的速度较快。缺点:由于类加载的时候就完成了对象的创建,有的时候我们无需调用的情况下,对象已经存在,这样的话就会造成内存浪费。

当前硬件和服务器的发展,快于软件的发展,另外的,微服务和集群化部署,大大降低了横向扩展的门槛和成本,所以老猫觉得当前的内存其实是不值钱的,所以上述这种单例模式硬说其缺点有多严重其实也不然,个人觉得这种模式用于实际开发过程中其实是没有问题的。

其实在我们日常使用的spring框架中,IOC容器本身就是一个饿汉式单例模式,spring启动的时候就将对象加载到了内存中,这里咱们不做展开,等到后续咱们梳理到spring源代码的时候再展开来说。

懒汉式单例模式

上述饿汉单例模式我们说它的缺点是浪费内存,因为其在类加载的时候就创建了对象,那么针对这种内存浪费的解决方案,我们就有了“懒汉模式”。对于这种类型的单例模式,老猫是这么理解的,懒汉的定义给人的直观感觉是懒惰、拖延。那么对应的模式上来说,这种方案创建对象的方法也是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象,否则则先执行实例化操作。

从上述的输出中我们很容易地发现,两个线程中所获取的对象是不同的,当然这个是有一定概率性质的。所以在这种多线程请求的场景下,就出现了线程安全性问题。

聊到共享变量访问线程安全性的问题,我们往往就想到了锁,所以,咱们在原有的代码块上加上锁对其优化试试,我们首先想到的是给方法代码块加上锁。

从上图中我们看到虽然LazySingleton不是null,但是指向的空间并没有初始化,最终被业务使用的时候还是会报错,这就是DCL失效的问题,这种问题难以跟踪难以重现可能会隐藏很久。

JDK1.5之前JMM(Java Memory Model,即Java内存模型)中的Cache、寄存器到主存的回写规定,上面第二第三的顺序无法保证。JDK1.5之后,SUN官方调整了JVM,具体化了volatile关键字,private volatile static LazySingleton lazySingleton;只要加上volatile,就可以保证每次从主存中读取(这涉及到CPU缓存一致性问题,感兴趣的小伙伴可以研究研究),也可以防止指令重排序的发生,避免拿到未完成初始化的对象。

上面这种方式可以有效降低锁的竞争,锁不会将整个方法全部锁定,而是锁定了某个代码块。其实完全做完调试之后我们还是会发现锁争夺的问题并没有完全解决,用到了锁肯定会对整个代码的执行效率带来一定的影响。所以是否存在保证线程的安全,并且能够不浪费内存完美的解决方案呢?一起看下下面的解决方案。

内部静态类单例模式

这种方式其实是利用了静态对象创建的特性来解决上述内存浪费以及线程不安全的问题。
在这里我们要弄清楚,被static修饰的属性,类加载的时候,基本属性就已经加载完毕,但是静态方法却不会加载的时候自动执行,而是等到被调用之后才会执行。并且被STATIC修饰的变量JVM只为静态分配一次内存。(这里老猫不展开去聊static相关知识点,有兴趣的小伙伴也可以自行去了解一下更多JAVA中static关键字修饰之后的类、属性、方法的加载机制以及存储机制)

当然在上述中,针对赋值的方式老猫用了static代码块自动类加载的时候就创建好了对象,大家也可以做一下其他优化。不过还是得要保证单例模式。判断是否为单例模式,老猫这里有个比较粗糙的办法。我们打印出成员对象变量的值,通过多次调用看看其值是否一样即可。当然如果大家还有其他好办法也欢迎留言。

总结

针对单例模式相信大家对其有了一个不错的认识了。在日常开发的过程中,其实我们都接触过,spring框架中,IOC容器本身就是单例模式的,当然上述老猫也有提及到。框架中的单例模式,咱们等全部梳理完毕设计模式之后再去做深入探讨。

关于单例模式的优点也是显而易见的:

提供了对惟一实例的受控访问。因为在系统内存中只存在一个对象,所以能够节约系统资源,对于一些须要频繁建立和销毁的对象单例模式无疑能够提升系统的性能。

那么缺点呢?大家有想过么?我们就拿上面的线程池创建这个例子来说事儿。我们整个业务系统其实有很多类别的线程池,如果说我们根据不同的业务类型去做线程池创建的拆分的话,咱们是不是需要写很多个这样的单例模式。那么对于实际的开发过程中肯定是不友好的。
所以主要缺点可想而知。

因为单利模式中没有抽象层,所以单例类的扩展有很大的困难。从开发者角度来说,使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。

散热风扇https://www.uv-semi.com/

深圳网站建设www.sz886.com

本博文版权归博主所有,转载请注明地址!如有侵权、违法,请联系admin@php.cn举报处理!
全部评论 文明上网理性发言,请遵守新闻评论服务协议
0条评论
作者最新博文
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号

  • 登录PHP中文网,和优秀的人一起学习!
    全站2000+教程免费学