1.单例设计模式
单例模式:保证一个类仅有一个实例,并提供一个该实例的全局访问点。——《设计模式》
为什么需要?系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
上述定义总结以下特点大致有3点:
(1)单例类只有一个实例对象; (2)该单例对象必须由单例类自行创建; (3)单例类对外提供一个访问该单例的全局访问点。
单例的应用场景
单例模式的核心精髓其实是避免创建不必要的对象
不必要的对象 一般是:
(1)频繁创建的一些类,又频繁被销毁
(2)“昂贵的对象”,有些对象创建的成本比其他对象要高得多,比如占用资源较多,或实例化耗时较长
(3)系统要求单一控制逻辑的操作,或者对象需要被共享的情况
常见的使用场合:数据库的连接池、Spring中的全局访问点BeanFactory,Spring下的Bean、多线程的线程池、网络连接池等等 单例模式的
优点:
不仅可以减少每次创建对象的时间开销,还可以节约内存空间; 能够避免由于操作多个实例导致的逻辑错误;
如果一个对象有可能贯穿整个应用程序,能够起到了全局统一管理控制的作用。
缺点:
单例模式一般没有接口,没有抽象层,扩展困难。如果要扩展,得修改原来的代码。
单例模式的功能代码通常写在一个类中,其职责过重,如果功能设计不合理,则很容易违背单一职责原则,
不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。比如单例模式下去将对象转成json 会出现互相引用的问题。
单例模式五种实现方式:
1、饿汉式(线程安全,调用效率高,但是不能延时加载)
public class ImageLoader{ private static ImageLoader instance = new ImageLoader; private ImageLoader(){} public static ImageLoader getInstance(){ return instance; } }
一上来就把单例对象创建出来了,要用的时候直接返回即可,这种可以说是单例模式中最简单的一种实现方式。
预加载:单例在还没有使用到的时候,初始化就已经完成了。也就是说,如果程序从头到位都没用使用这个单例的话,单例的对象还是会创建。这就造成了不必要的资源浪费。所以不推荐这种实现方式。
2.懒汉式(线程安全,调用效率不高,但是能延时加载):懒加载
public class SingletonDemo2 {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
private static SingletonDemo2 instance;
//构造器私有化
private SingletonDemo2(){}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if(instance==null){
instance=new SingletonDemo2();
}
return instance;
}
}
效率太低,每个线程在想获得类的实例时候,执行 getInstance() 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低要改进。
3.Double CheckLock实现单例:DCL也就是双重锁判断机制(由于JVM底层模型原因,偶尔会出问题,不建议使用):
public class SingletonDemo5 { private volatile static SingletonDemo5 SingletonDemo5; private SingletonDemo5() { } public static SingletonDemo5 newInstance() { if (SingletonDemo5 == null) { synchronized (SingletonDemo5.class) { if (SingletonDemo5 == null) { SingletonDemo5 = new SingletonDemo5(); } } } return SingletonDemo5; } }
(1)这种方式为什么要检查两次?检查一次行不行?
第一次判断singleton == null作用是,如果单例对象已经创建,就直接返回对象,不需要进入同步锁,提高效率
第二次判断singleton == null,假如没有这个判断,多个线程同时通过第一次singleton == null之后,只有一个线程进入临界区,其他线程排队,没有第二次检查的话,会创建多个实例,第二次是必须检查
(2)在代码1处使用了关键字volatile,为什么需要?
因为 JVM 指令重排序,也就是并不限制处理器的指令顺序,说白了就是在不影响结果的情况下,顺序可能会被打乱。
4.静态内部类实现模式(线程安全,调用效率高,可以延时加载)
public class SingletonDemo3 { private static class SingletonClassInstance{ private static final SingletonDemo3 instance=new SingletonDemo3(); } private SingletonDemo3(){} public static SingletonDemo3 getInstance(){ return SingletonClassInstance.instance; } }
5.枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)
public enum SingletonDemo4 { //枚举元素本身就是单例 INSTANCE; //添加自己需要的操作 public void singletonOperation(){ } }
如何选用:
-单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
-单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式
饿汉式/静态内部类/枚举是通用什么方式保证线程安全?
以上的静态内部类、饿汉等模式均是通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。
这其实是利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。
所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。
扩展讨论:
那么这些方式都是和synchronized有关系的,有没有哪种方式可以完全不使用synchronized的吗?
6.CAS(AtomicReference)实现单例模式
public class Singleton { private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } }
优点:用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。(CAS具体可以在JUC那块分享)
缺点:CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。
另外,代码中,如果N个线程同时执行到 singleton = new Singleton();的时候,会有大量对象被创建,可能导致内存溢出。
7.ThreadLocal实现单例模式
这种方式仅了解,不推荐使用,因为它其实失去了单例的意义
ThreadLocal 和 同步模式创建的单例区别:
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。对于多线程资源共享的问题,同步机制(synchronized)采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。这种是同步机制仅提供一份变量,让不同的线程排队访问,而ThreadLocal为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
2022-11-16 力扣19 删除链表的倒数第N个结点