单例模式是非常常见的设计模式,其含义也很简单,一个类给外部提供一个唯一的实例。下文所有的代码均在github

源码整个项目不仅仅有设计模式,还有其他JavaSE知识点,欢迎Star,Fork
单例模式的UML图
单例模式的关键点
通过上面的UML图,我们可以看出单例模式的特点如下:
1、构造器是私有的,不允许外部的类调用构造器
2、提供一个供外部访问的方法,该方法返回单例类的实例
如何实现单例模式
上面已经给出了单例模式的关键点,我们的实现只需要满足上面2点即可。但是正因为单例模式的实现方式比较宽松,所以不同的实现方式会有不同的问题。我们可以对单例模式的实现做一下分类,看一看有哪些不同的实现方式。
1根据单例对象的创建时机不同,可以分为饿汉模式和懒汉模式。饿汉是指在类加载的时候,就创建了对象。但是创建对象有时比较消耗资源,会造成类加载很慢,但是优点是获取对象的速度很快,因为早已经创建好了嘛。懒汉就是相对饿汉而言,在需要返回单例对象的时候,在创建对象,类加载的时候,并不初始化,好处与缺点也不言而喻
2.根据是否实现线程安全,可以分为普通的懒汉模式这种线程不安全的写法,和饿汉模式,双重检查锁的懒汉模式,以及通过静态内部类或者枚举类等实现的线程安全的写法。
一个线程不安全的单例模式
public class SimpleSingleton {
private static SimpleSingleton simpleSingleton;
private SimpleSingleton(){
}
public static SimpleSingleton getInstance(){
if (simpleSingleton == null) {
simpleSingleton = new SimpleSingleton();
}
return simpleSingleton;
}
}
首先,我们可以看出这是一个懒汉模式的实现。因为只有在getInstance的时候,才会真正创建单例的对象。但是为什么他是线程不安全的呢,是因为可能会有2个线程同时进入if (simpleSingleton == null)的判断,就是同时创建了simpleSingleton对象。
DCL懒汉模式
上面的方法可以看出是存在线程不安全的问题的,我们可以用同步关键字synchronized来实现线程安全。我们先逐步分析,先用synchronized来改写上面的懒汉模式,代码如下:
public class DCLSingleton {
private static DCLSingleton singleton;
private DCLSingleton(){
}
public synchronized static DClSingleton getSingleton(){
if (singleton == null) {
singleton = new DCLSingleton();
}
return singleton;
}
}
这样,就有效的保证了不会有两个线程同时执行该方法,但这个效率也太低了吧。因为在创建实例之后,每次得到实例对象,还是需要进行同步,synchronized的同步保证代价是比较大的,因此可以在此基础上进行改造。在已经创建好之后,就不需要同步了,我们可以改成如下的形式:
public static DCLSingleton getSingleton(){
if (singleton == null) {
synchronized (DCLSingleton.class) {
if (singleton == null) {
singleton = new DCLSingleton();
}
}
}
return singleton;
}
其他代码不变,只看这个方法。该方法的两重if (singleton == null)可以有效地保证线程安全。比如,当两个线程同时进入该方法的时候,第一个if,两者都是进入,下面的代码,但是碰到同步代码块,只能有一个先进入,进入的时候,继续判断,再次判断为空,才会真正创建对象。如果不进行,第二个判断,那些对于第一个进入的线程而言,确实创建了对象,但是第二个线程,他紧接着也会执行创建对象的操作,因为不知道第一个线程已经创建成功。因此,需要两次判空。
但是真的就如此简单的保证了线程安全吗?我们仔细分析一下这个过程,singleton = new DCLSingleton();这个代码实际上是3个操作。
1.给DCLSingleton实例分配内存
2.调用DCLSingleton()的构造函数,初始化成员字段
3.将singleton对象指向分配的内存空间。
在JDK1.5以前,上面的3个执行顺序是不固定的,有可能是1-2-3,或者1-3-2。如果是1-3-2,则在第一个线程执行完第三步以后,第二个线程立即执行,但还没有真正的进行初始化,所以就会使用的时候出错。在JDK1.5以后,我们可以用volatile关键字来保证该1-2-3的顺序执行。所以,除了getSingleton()方法要改成上面的样子以外,还需要对private static DCLSingleton singleton; 改写成private static volatile DCLSingleton singleton; 这样,就真正保证了线程同步的懒汉写法的单例模式。
饿汉写法
饿汉写法有很多变形,但无论是哪一种变形,都能保证线程安全,因为饿汉写法是在类加载的时候,就完成了对象的初始化,类加载保证了他们天生是线程安全的。下面给出常见的2中饿汉写法
public class HungrySingleton {
private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getSingleton(){
return singleton;
}
}
public class HungrySingleton {
private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){
}
// public static HungrySingleton getSingleton(){
// return singleton;
// }
}
这两种对初始化单例的对象上面,都是一致的, 通过final来保证对象的唯一。不同的是,调用单例对象的方式,第一种是通过getSingleton(),第二种是通过类.类变量的形式。
静态内部类实现单例模式
双重检查锁(DCL)实现单例模式,虽然解决了线程不安全的问题,以及保证了资源的懒加载,在需要的时候,才会进行实例化的操作。但是在某些情况下(比如JDK低于1.5)会出现DCL失效,所以有一种很简洁且依旧是懒加载的方法实现单例模式。写法如下:
public class StaticSingleton {
private StaticSingleton(){
}
public static final StaticSingleton getInstance(){
return Holder.singleton;
}
private static class Holder{
private static final StaticSingleton singleton = new StaticSingleton();
}
}
通过静态内部类的形式,实现单例类的初始化,其特性同样是通过ClassLoader来保证其单例对象的唯一,但是这是懒加载的,因为只有在Holder类被调用的时候,即getInstance调用的时候,才会加载Holder类从而实现创建对象。
枚举类实现单例模式
直接看代码:
public enum EnumSingleton {
SINGLETON;
public void doSometings(){
}
}
使用的时候,直接通过EnumSingleton.SINGLETON.doSomethings()。枚举类天生特性是保证不会有两个实例,并且只有在第一次访问的时候才会被实例化,是懒加载的情况。
真的不会再次创建新的对象吗?
在常规调用单例类的getInstance()方法的情况下,使用线程安全的写法确实不会创建新的对象,但是Java提供了很多奇特的技巧和使用,下面这些使用会破坏掉常规的单例。
在除了枚举实现单例模式的方法以外,其余所有方法碰到上述四种情况,都会重新创建对象。原因如下:
什么时候用单例模式,用哪一种写法的单例模式
单例模式有两种比较适合的使用场景。
第一种是创建某个对象,需要的代价比较大,为了避免频繁的创建和销毁对象从而引起的对资源的浪费,会考虑使用单例模式。
第二种是这个对象必须只有一个,有多个会造成不可预估的错误,或者程序的混乱,比如只会有一个序号生成器,一个缓存等等。
针对使用的单例模式,如果需要理解的加载资源,就是用饿汉写法,在Android应用中,很多对象需要在启动的时候,立即就使用,比如启动时,需要拉取相机配置的类管理缩略图的cache类等等。如果不是立即需要,或者不是贯穿应用始终的,就不需要使用饿汉写法,可以考虑懒汉写法用(DCL或者静态内部类实现)这两种在一般情况下都不会出现问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# java
# 单例模式
# java设计模式之单例模式学习
# 浅析Java设计模式编程中的单例模式和简单工厂模式
# Java设计模式之单例模式实例详解【懒汉式与饿汉式】
# java设计模式之单例模式的详解及优点
# 深入解析Java的设计模式编程中单例模式的使用
# Java设计模式之单例模式实例分析
# 简单讲解在Java编程中实现设计模式中的单例模式结构
# java 设计模式之单例模式
# Java设计模式系列之深入浅出单例模式
# 加载
# 才会
# 第一个
# 多个
# 都是
# 不安全
# 我们可以
# 第二个
# 可以看出
# 就会
# 会有
# 就不
# 可以用
# 重写
# 比较大
# 提供一个
# 这两种
# 哪一种
# 第二种
# 第一种
相关文章:
济南网站建设制作公司,室内设计网站一般都有哪些功能?
南京网站制作费用,南京远驱官方网站?
盐城做公司网站,江苏电子版退休证办理流程?
C#如何序列化对象为XML XmlSerializer用法
已有域名如何快速搭建专属网站?
建站一年半SEO优化实战指南:核心词挖掘与长尾流量提升策略
C#如何在一个XML文件中查找并替换文本内容
建站之星安装后如何配置SEO及设计样式?
淘宝制作网站有哪些,淘宝网官网主页?
,网页ppt怎么弄成自己的ppt?
如何在云主机上快速搭建网站?
如何快速建站并高效导出源代码?
高端云建站费用究竟需要多少预算?
Dapper的Execute方法的返回值是什么意思 Dapper Execute返回值详解
如何在Golang中指定模块版本_使用go.mod控制版本号
html制作网站的步骤有哪些,iapp如何添加网页?
如何快速查询域名建站关键信息?
如何使用Golang安装API文档生成工具_快速生成接口文档
建站主机是否等同于虚拟主机?
义乌企业网站制作公司,请问义乌比较好的批发小商品的网站是什么?
建站之星2.7模板快速切换与批量管理功能操作指南
网站图片在线制作软件,怎么在图片上做链接?
宝塔建站后网页无法访问如何解决?
如何在云服务器上快速搭建个人网站?
网站制作员失业,怎样查看自己网站的注册者?
车管所网站制作流程,交警当场开简易程序处罚决定书,在交警网站查询不到怎么办?
如何选择高效稳定的ISP建站解决方案?
如何在香港服务器上快速搭建免备案网站?
北京网站制作网页,网站升级改版需要多久?
企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?
如何通过WDCP绑定主域名及创建子域名站点?
,南京靠谱的征婚网站?
建站之星好吗?新手能否轻松上手建站?
如何在Golang中实现微服务服务拆分_Golang微服务拆分与接口管理方法
如何快速搭建虚拟主机网站?新手必看指南
MySQL查询结果复制到新表的方法(更新、插入)
网站制作多少钱一个,建一个论坛网站大约需要多少钱?
如何访问已购建站主机并解决登录问题?
定制建站策划方案_专业建站与网站建设方案一站式指南
教程网站设计制作软件,怎么创建自己的一个网站?
建站168自助建站系统:快速模板定制与SEO优化指南
网站代码制作软件有哪些,如何生成自己网站的代码?
建站之星后台管理如何实现高效配置?
c# await 一个已经完成的Task会发生什么
,sp开头的版面叫什么?
网站制作需要会哪些技术,建立一个网站要花费多少?
品牌网站制作公司有哪些,买正品品牌一般去哪个网站买?
建站主机服务器选购指南:轻量应用与VPS配置解析
如何确保西部建站助手FTP传输的安全性?
网站制作和推广的区别,想自己建立一个网站做推广,有什么快捷方法马上做好一个网站?
*请认真填写需求信息,我们会在24小时内与您取得联系。