五种单例模式介绍

简介: 五种单例模式介绍

第一种(懒汉,线程安全):

publicclassSingleton {     
privatestaticSingletoninstance;     
privateSingleton (){}     
publicstaticsynchronizedSingletongetInstance() {     
if (instance==null) {     
instance=newSingleton();     
    }     
returninstance;     
    }  
} 

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。


第二种(饿汉):

publicclassSingleton {  
privatestaticSingletoninstance=newSingleton();  
privateSingleton (){}  
publicstaticSingletongetInstance() {  
returninstance;  
    }  
} 

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。


第三种(双重校验锁):

publicclassSingleton {  
privatevolatilestaticSingletonsingleton;  
privateSingleton (){}  
publicstaticSingletongetSingleton() {  
if (singleton==null) {  
synchronized (Singleton.class) {  
if (singleton==null) {  
singleton=newSingleton();  
        }  
        }  
    }  
returnsingleton;  
    }  
}

需要加volatile关键字是应为new Singleton() 并不是一个原子性的操作;在指令执行的时候存在乱序执行的情况,volatile解决了这个问题


第四种(静态内部类):

publicclassSingleton {  
privatestaticclassSingletonHolder {  
privatestaticfinalSingletonINSTANCE=newSingleton();  
    }  
privateSingleton (){}  
publicstaticfinalSingletongetInstance() {  
returnSingletonHolder.INSTANCE;  
    }  
}  

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingletonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

那么,静态内部类又是如何实现线程安全的呢?首先,我们先了解下类的加载时机。

类加载时机:JAVA虚拟机在有且仅有的5种场景下会对类进行初始化。

1.遇到newgetstaticsetstatic或者invokestatic4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。

2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。

3.当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。

4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。

5.当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStaticREF_putStaticREF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

5种情况被称为是类的主动引用,注意,这里《虚拟机规范》中使用的限定词是"有且仅有",那么,除此之外的所有引用类都不会对类进行初始化,称为被动引用。静态内部类就属于被动引用的行列。

我们再回头看下getInstance()方法,调用的是SingletonHoler.INSTANCE,取的是SingleTonHoler里的INSTANCE对象,跟上面那个DCL方法不同的是,getInstance()方法并没有多次去new对象,故不管多少个线程去调用getInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建,然后再被getInstance()方法返回出去,这点同饿汉模式。那么INSTANCE在创建过程中又是如何保证线程安全的呢?在《深入理解JAVA虚拟机》中,有这么一句话:

虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。

故而,可以看出INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

那么,是不是可以说静态内部类单例就是最完美的单例模式了呢?其实不然,静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以在静态内部类与DCL模式里自己斟酌。


第五种(枚举):

publicenumSingleton {  
INSTANCE;  
publicvoidwhateverMethod() {  
    }  
} 

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

目录
相关文章
|
Kubernetes 网络协议 数据安全/隐私保护
WireGuard 系列文章(八):基于 WireGuard 的 K8S CNI Kilo 简介
WireGuard 系列文章(八):基于 WireGuard 的 K8S CNI Kilo 简介
|
Android开发
autojs修改悬浮窗按钮点击事件
牙叔教程 简单易懂
1601 0
|
11月前
|
人工智能
2025年人工智能与可持续发展国际学术会议 2025 International Conference on Artificial Intelligence and Sustainable Development (ICAISD 2025)
2025年人工智能与可持续发展国际学术会议 2025 International Conference on Artificial Intelligence and Sustainable Development (ICAISD 2025)
702 7
|
11月前
|
存储 弹性计算 缓存
阿里云服务器通用型g8i实例性能与使用场景介绍及最新收费标准参考
阿里云ECS通用型g8i服务器采用阿里云全新CIPU架构,可提供稳定的算力输出、更强劲的I/O引擎以及芯片级的安全加固。ECS通用型g8i实例支持开启或关闭超线程配置,单台g8i实例最高支持100万IOPS。阿里云ECS通用型g8i实例CPU采用Intel®Xeon®Emerald Rapids或者Intel®Xeon®Sapphire Rapids,主频不低于2.7 GHz,全核睿频3.2GHz。本文为大家介绍通用型g8i实例性能与使用场景介绍及最新收费标准,以供参考。
|
存储 SQL 关系型数据库
MySQL高级篇——索引失效的11种情况
索引优化思路、要尽量满足全值匹配、最佳左前缀法则、主键插入顺序尽量自增、计算、函数导致索引失效、类型转换(手动或自动)导致索引失效、范围条件右边的列索引失效、不等于符号导致索引失效、is not null、not like无法使用索引、左模糊查询导致索引失效、“OR”前后存在非索引列,导致索引失效、不同字符集导致索引失败,建议utf8mb4
MySQL高级篇——索引失效的11种情况
|
11月前
|
存储 缓存 监控
探索微服务架构中的API网关模式
探索微服务架构中的API网关模式
149 2
|
SQL 安全 关系型数据库
PostgreSQL SQL注入漏洞(CVE-2018-10915)--处理
【8月更文挑战第8天】漏洞描述:PostgreSQL是一款自由的对象关系型数据库管理系统,支持多种SQL标准及特性。存在SQL注入漏洞,源于应用未有效验证外部输入的SQL语句,允许攻击者执行非法命令。受影响版本包括10.5及更早版本等。解决方法为升级PostgreSQL
696 2
|
监控 网络安全 PHP
对象存储oss使用问题之操作报错:Unable to execute HTTP request: SocketException如何解决
《对象存储OSS操作报错合集》精选了用户在使用阿里云对象存储服务(OSS)过程中出现的各种常见及疑难报错情况,包括但不限于权限问题、上传下载异常、Bucket配置错误、网络连接问题、跨域资源共享(CORS)设定错误、数据一致性问题以及API调用失败等场景。为用户降低故障排查时间,确保OSS服务的稳定运行与高效利用。
6016 0
|
存储 缓存 NoSQL
redis中的分布式锁(setIfAbsent)(expire)
redis中的分布式锁(setIfAbsent)(expire)