CSDN地址:https://blog.csdn.net/weixin_45388036/article/details/105891362
引言: 学习笔记记录开始啦,接下来我会分享最常见的设计模式。今天开始第一篇:单例模式
微云学习地址:https://share.weiyun.com/50XCTqT
网盘学习地址:https://pan.baidu.com/s/1DHGGkMBmg7wmz6RBvLshLQ 提取码:y8t3
一:概念及理解
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。
常见单例举例:ServletContext、ServletContextConfig ;在 Spring 框架应用中 ApplicationContext;数据库的连接池DBPool也都是单例形式。
优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。避免对资源的多重占用。
缺点:没有接口,不能继承,要修改只能修改代码
单例共有特征:
构造方法私有化(保证对象只能自己来创建)
自己在内部创建自己的实例
提供一个全局的访问点(一般是static方法)是拿到单例的入口
二、单例分类及详解
1、饿汉式单例
在单例类首次加载时就立即初始化,并且创建单例对象。不管有没有使用,先创建了再说。
![](https://img.haomeiwen.com/i23244546/635a0df74bd677d9.png)
第二种饿汉式单例写法:利用static方法块,在类加载时就构建了唯一对象;
![](https://img.haomeiwen.com/i23244546/5290d75fb57e20ef.png)
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费内存空间,如果从来没有使用也都创建了,小范围内影响不大,但是大范围使用不建议使用。
为了改善内存浪费,我们可以在使用到单例实例时再创建,这就时下面的懒汉式单例
2、懒汉式单例
特点:当用户要使用单例时才创建对象。(线程不安全)
![](https://img.haomeiwen.com/i23244546/1be2d8e91a1727b5.png)
以上懒汉式单例的实现是线程不安全的,并发环境下很可能出现多个Singleton实例,不同线程进入到if判断语句当中时,会出现创建多个不同实例的情况,偏离单例设计模式的初衷。
改进方法:通过synchronized来解决。
第一种:在getInstance方法上加同步synchronized(线程安全)
![](https://img.haomeiwen.com/i23244546/87d6d13672f5bfe9.png)
缺点:在getInstance方法上用synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。于是下面第二种是更好的方式,既兼顾线程安全又提升程序性能
第二种:双重检查锁的单例模式,在getSingleton方法中对singleton进行两次判空。(线程安全)
![](https://img.haomeiwen.com/i23244546/30cf3924c26fc536.png)
更优方案:synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。以下采用静态内部类的方式:
第三种:静态内部类方式(内部类先于外部类加载,调用getInstance方法时,内部类逻辑才会执行。性能最优的写法)(线程安全)
![](https://img.haomeiwen.com/i23244546/8fa159a8a67e2e01.png)
高级内容:(这里只做大概说明,具体可参考我头部链接一起学习)
1、反射破坏单例
我们前面所介绍的,单例模式必须满足构造方法私有化,避免外部实例化,而Java反射机制是能够实例化构造方法为private的类的,使所有的Java单例实现失效。
解决办法:在构造函数中抛出异常,提醒开发人员,不允许用反射机制创建单例。
![](https://img.haomeiwen.com/i23244546/e5f0829ccd0ad521.png)
2、序列化破坏单例
当我们将一个单例对象创建好,序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。
解决办法:在单例类中加上以下方法,覆盖了序列化对象,反序列化出来的对象,还是创建了两次,发生在JVM层次,之前反序列化出来的对象被GC回收。
![](https://img.haomeiwen.com/i23244546/cf6f0d404c7d5856.png)
3、注册式单例
解决了序列化问题,最安全单例;
每一个实例都登记到某一个地方,使用唯一的标识获取实例,一种为容器缓存,一种为枚举登记,下面介绍其中一种;
![](https://img.haomeiwen.com/i23244546/7aab0d5d6815214f.png)
4、ThreadLocal单例
ThreadLocal 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。
![](https://img.haomeiwen.com/i23244546/a3f4c9876043e9eb.png)
单例模式小结:
单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。
单例模式关键点:
构造函数方法为Private修饰。
通过一个静态方法或者枚举返回单例类对象。
需要确保单例类的对象有且只有一个,尤其是在多线程环境下。
确保单例类对象在反序列化时不会重新构建对象。
重点掌握饿汉式与懒汉式两种单例模型。
饿汉式:不用也加载。线程安全的,可以直接用于多线程而不会出现问题,但会出现内存浪费情况,不推荐大范围使用
懒汉式:用时再加载。非线程安全,可通过getInstance方法上加同步synchronized、双重检查锁、静态内部类三种方式改善;
其中第一种每次都同步,性能不好,第二种两次非空检查,避免每次同步的性能损耗,第三种没有性能损耗。
网友评论