来源:小小木的博客

www.cnblogs.com/wyc1994666/p/11394755.html

1. 单例模式常见问题

为什么要有单例模式

单例模式是一种设计模式,它限制了实例化一个对象的行为,始终至多只有一个实例。当只需要一个对象来协调整个系统的操作时,这种模式就非常有用.它描述了如何解决重复出现的设计问题,

比如我们项目中的配置工具类,日志工具类等等。

如何设计单例模式 ?

1.单例类如何控制其实例化

2.如何确保只有一个实例

通过一下措施解决这些问题:

private构造函数,类的实例话不对外开放,由自己内部来完成这个操作,确保永远不会从类外部实例化类,避免外部随意new出来新的实例。

该实例通常存储为私有静态变量,提供一个静态方法,返回对实例的引用。如果是在多线程环境下则用锁或者内部类来解决线程安全性问题。

2. 单例类有哪些特点 ?

私有构造函数
它将阻止从类外部实例化新对象

它应该只有一个实例
这是通过在类中提供实例来方法完成的,阻止外部类或子类来创建实例。这是通过在java中使构造函数私有来完成的,这样任何类都不能访问构造函数,因此无法实例化它。

单实例应该是全局可访问的
单例类的实例应该是全局可访问的,以便每个类都可以使用它。在Java中,它是通过使实例的访问说明符为public来完成的。

节省内存,减少GC

因为是全局至多只有一个实例,避免了到处new对象,造成浪费内存,以及GC,有了单例模式可以避免这些问题。

3. 单例模式8种写法

下面由我给大家介绍8种单例模式的写法,各有千秋,存在即合理,通过自己的使用场景选一款使用即可。我们选择单例模式时的挑选标准或者说评估一种单例模式写法的优劣时通常会根据一下两种因素来衡量:

1.在多线程环境下行为是否线程安全

2.饿汉以及懒汉

3.编码是否优雅(理解起来是否比较直观)

1. 饿汉式线程安全的

public class SingleTon{private static final SingleTon INSTANCE = new SingleTon();private SingleTon(){ }public static SingleTon getInstance(){return INSTANCE;}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);}}

这种写法是非常简单实用的,值得推荐,唯一缺点就是懒汉式的,也就是说不管是否需要用到这个方法,当类加载的时候都会生成一个对象。

除此之外,这种写法是线程安全的。类加载到内存后,就实例化一个单例,JVM保证线程安全。关注公众号互联网架构师回复2T获取架构师视频教程。

2. 饿汉式线程安全(变种写法)

public class SingleTon{private static final SingleTon INSTANCE ;static {INSTANCE = new SingleTon(); }private SingleTon(){}public static SingleTon getInstance(){return INSTANCE;}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);}}

3. 懒汉式线程不安全

public class SingleTon{private static  SingleTon instance ;private SingleTon(){}public static SingleTon getInstance(){if(instance == null){instance = new SingleTon();}return instance;}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);// 通过开启100个线程 比较是否是相同对象for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();}}}

这种写法虽然达到了按需初始化的目的,但却带来线程不安全的问题,至于为什么在并发情况下上述的例子是不安全的呢 ?

// 通过开启100个线程 比较是否是相同对象
for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();
}

为了使效果更直观一点我们对getInstance 方法稍做修改,每个线程进入之后休眠一毫秒,这样做的目的是为了每个线程都尽可能获得cpu时间片去执行。代码如下

public static SingleTon getInstance(){if(instance == null){try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}instance = new SingleTon();}return instance;
}

执行结果如下

上述的单例写法,我们是可以创造出多个实例的,至于为什么在这里要稍微解释一下,这里涉及了同步问题

造成线程不安全的原因:

当并发访问的时候,第一个调用getInstance方法的线程t1,在判断完singleton是null的时候,线程A就进入了if块准备创造实例,但是同时另外一个线程B在线程A还未创造出实例之前,就又进行了singleton是否为null的判断,这时singleton依然为null,所以线程B也会进入if块去创造实例,这时问题就出来了,有两个线程都进入了if块去创造实例,结果就造成单例模式并非单例。

注:这里通过休眠一毫秒来模拟线程挂起,为初始化完instance


为了解决这个问题,我们可以采取加锁措施,所以有了下面这种写法

4. 懒汉式线程安全(粗粒度Synchronized)

public class SingleTon{private static  SingleTon instance ;private SingleTon(){}public static SingleTon synchronized getInstance(){if(instance == null){instance = new SingleTon();}return instance;}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);// 通过开启100个线程 比较是否是相同对象for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();}}}

由于第三种方式出现了线程不安全的问题,所以对getInstance方法加了synchronized来保证多线程环境下的线程安全性问题,这种做法虽解决了多线程问题但是效率比较低。

因为锁住了整个方法,其他进入的现成都只能阻塞等待了,这样会造成很多无谓的等待。

于是可能有人会想到可不可以让锁的粒度更细一点,只锁住相关代码块可否?所以有了第五种写法。关注公众号互联网架构师回复2T获取我整理的系列Java多线程教程。

5. 懒汉式线程不安全(synchronized代码块)

public class SingleTon{private static  SingleTon instance ;private SingleTon(){}public static SingleTon getInstance(){if(insatnce == null){synchronied(SingleTon.class){instance = new SingleTon();}}return instance;}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);// 通过开启100个线程 比较是否是相同对象for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();}}}

当并发访问的时候,第一个调用getInstance方法的线程t1,在判断完instance是null的时候,线程A就进入了if块并且持有了synchronized锁,但是同时另外一个线程t2在线程t1还未创造出实例之前,就又进行了instance是否为null的判断,这时instance依然为null,所以线程t2也会进入if块去创造实例,他会在synchronized代码外面阻塞等待,直到t1释放锁,这时问题就出来了,有两个线程都实例化了新的对象。

造成这个问题的原因就是线程进入了if块并且在等待synchronized锁的过程中有可能上一个线程已经创建了实例,所以进入synchronized代码块之后还需要在判断一次,于是有了下面这种双重检验锁的写法。

6. 懒汉式线程安全(双重检验加锁)

public class SingleTon{private static  volatile SingleTon instance ;private SingleTon(){}public static SingleTon getInstance(){if(instance == null){synchronied(SingleTon.class){if(instance == null){instance = new SingleTon();}}}return instance;}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);// 通过开启100个线程 比较是否是相同对象for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();}}}

这种写法基本趋于完美了,但是可能需要对一下几点需要进行解释:

  • 第一个判空(外层)的作用 ?

  • 第二个判空(内层)的作用 ?

  • 为什么变量修饰为volatile ?

第一个判空(外层)的作用

首先,思考一下可不可以去掉最外层的判断?答案是:可以

其实仔细观察之后会发现最外层的判断跟能否线程安全正确生成单例无关!!!

它的作用是避免每次进来都要加锁或者等待锁,有了同步代码块之外的判断之后省了很多事,当我们的单例类实例化一个单例之后其他后续的所有请求都没必要在进入同步代码块继续往下执行了,直接返回我们曾生成的实例即可,也就是实例还未创建时才进行同步,否则就直接返回,这样就节省了很多无谓的线程等待时间,所以最外的判断可以认为是对提升性能有帮助。

第二个判空(内层)的作用

假设我们去掉同步块中的是否为null的判断,有这样一种情况,A线程和B线程都在同步块外面判断了instance为null,结果t1线程首先获得了线程锁,进入了同步块,然后t1线程会创造一个实例,此时instance已经被赋予了实例,t1线程退出同步块,直接返回了第一个创造的实例,此时t2线程获得线程锁,也进入同步块,此时t1线程其实已经创造好了实例,t2线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以t2线程也会创造一个实例返回,此时就造成创造了多个实例的情况。

为什么变量修饰为volatile

因为虚拟机在执行创建实例的这一步操作的时候,其实是分了好几步去进行的,也就是说创建一个新的对象并非是原子性操作。在有些JVM中上述做法是没有问题的,但是有些情况下是会造成莫名的错误。关注公众号互联网架构师回复JVM获取我整理的系列JVM教程。

首先要明白在JVM创建新的对象时,主要要经过三步。

1.分配内存

2.初始化构造器

3.将对象指向分配的内存的地址

因为仅仅一个new 新实例的操作就涉及三个子操作,所以生成对象的操作不是原子操作。

而实际情况是,JVM会对以上三个指令进行调优,其中有一项就是调整指令的执行顺序(该操作由JIT编译器来完成)。

所以,在指令被排序的情况下可能会出现问题,假如 2和3的步骤是相反的,先将分配好的内存地址指给instance,然后再进行初始化构造器,这时候后面的线程去请求getInstance方法时,会认为instance对象已经实例化了,直接返回一个引用。

如果这时还没进行构造器初始化并且这个线程使用了instance的话,则会出现线程会指向一个未初始化构造器的对象现象,从而发生错误。

7. 静态内部类的方式(基本完美了)

public class SingleTon{public static SingleTon getInstance(){return StaticSingleTon.instance;}private static class StaticSingleTon{private static final SingleTon instance = new SingleTon();}public static void main(String[] args) {SingleTon instance1 = SingleTon.getInstance();SingleTon instance2 = SingleTon.getInstance();System.out.println(instance1 == instance2);// 通过开启100个线程 比较是否是相同对象for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();}}}
  • 因为一个类的静态属性只会在第一次加载类时初始化,这是JVM帮我们保证的,所以我们无需担心并发访问的问题。所以在初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。

  • 另外由于静态变量只初始化一次,所以singleton仍然是单例的。

8. 枚举类型的单例模式(太完美以至于。。。)

public Enum SingleTon{INSTANCE;public static void main(String[] args) {// 通过开启100个线程 比较是否是相同对象for(int i=0;i<100;i++){new Thread(()->System.out.println(SingleTon.getInstance().hashCode())).start();}}}

这种写法从语法上看来是完美的,他解决了上面7种写法都有的问题,就是我们可以通过反射可以生成新的实例。但是枚举的这种写法是无法通过反射来生成新的实例,因为枚举没有public构造方法


http://www.taodudu.cc/news/show-1750039.html

相关文章:

  • 对 Java 意义重大的 7 个性能指标
  • 2个月面了鹅厂的5个部门,心态差点奔溃了......
  • 网易开源分布式存储系统 Curve,性能彪悍!这是要吊打阿里?
  • 可怕!你没看错,这次确实是纯手工实现一个MyBatis框架!
  • 再见,ELK!
  • 太火了,这些牛逼的Java代码技巧,肯定能让你目瞪口呆!
  • 支付宝架构到底有多牛逼?看完这篇你就明白了!
  • 同事线上埋的这个坑,我整整找了3天3夜
  • 同事1000行又臭又长 的类!被我用IDEA几分钟重构!真香!
  • 腾讯面试题:如何实现一个类似新浪微博的短链接服务!
  • 惊呆了!JDK1.8竟然打破了我对接口的一切认知...
  • Hibernate 与 Mybatis 如何共存?打破你的认知!
  • Oracle用户可要顶住了:准备好大规模补丁工作!以修补多达 433 个的新安全漏洞...
  • Mybatis trim 标签的 2 个妙用!
  • 震惊!Redis 的字符串居然是这样实现的…
  • 别在网上乱找代码了,找了一段代码突然爆了!!!
  • 看图说话,FastJson 并没有那么流行!
  • GitLab 内置了 CI/CD 工具,强大啊!!
  • 推荐:Windows平台上三款提高工作效率的免费神器!
  • 怒爬某 Hub 资源就为撸了一个鉴黄平台
  • 被Redis击穿的一次面试经历
  • 彻底搞懂“红黑树”......
  • 我们公司不会用分布式事务!
  • 看看华为 Java 编程的军规...
  • 我的天!你竟然没有在SpringBoot中使用过异步请求和异步调用...
  • Spring Cloud 微服务下的权限解决方案
  • DataGrip 上手体验,真香!
  • 又整理了一批可以拿去做副业的开源项目...
  • 为什么我劝你放弃了Restful API?
  • 因为一条SQL,我差点被祭天......

8 种单例模式写法,助你搞定面试!相关推荐

  1. java hashmap用法_备战金九银十:Java核心技术面试题100+,助你搞定面试官

    一线互联网公司工作了几年,我作为求职者参加了不少面试,也作为面试官面试了很多同学,整理这份面试指南,一方面是帮助大家更好的准备面试,有的放矢,另一方面也是对自己知识框架做一个体系化的梳理. 这篇文章梳 ...

  2. 星火视频计算机原理,各专业上岸大神助你搞定专业课!| 初试专业课冲刺经验贴合集...

    原标题:各专业上岸大神助你搞定专业课!| 初试专业课冲刺经验贴合集 距离2021考研仅有 23天,为了能够帮助大家更高效地把握住考前最后的时间,小招特地收集了多篇学长学姐们的经验贴,从中整理出Hust ...

  3. 【面试锦囊】14种模式搞定面试算法编程题(8-14)

    面试锦囊之知识整理系列 面试锦囊系列一直有收到大家的反馈,包括后台内推成功的消息.朋友的同事从创业小公司成功跳到huawei等等,非常高兴小破号的这些整理分享能够真正地帮助到大家,以后也会继续.为了更 ...

  4. 轻松搞定面试中的二叉树题目

    版权全部,转载请注明出处,谢谢!http://blog.csdn.net/walkinginthewind/article/details/7518888 树是一种比較重要的数据结构,尤其是二叉树.二 ...

  5. 一文搞定面试中的二叉树问题

    一文搞定面试中的二叉树问题 版权所有,转载请注明出处,谢谢! http://blog.csdn.net/walkinginthewind/article/details/7518888 树是一种比较重 ...

  6. 如何在一分钟内搞定面试官

    转载自   如何在一分钟内搞定面试官 很多人的求职面试的过程中都会遇到这个问题:  "请做个自我介绍." 有的人,可以口若悬河.妙语连珠讲3分钟,有的人,可能磕磕巴巴,讲了30秒, ...

  7. java面试手写单链表_(转)面试大总结之一:Java搞定面试中的链表题目

    packageLinkedListSummary; importjava.util.HashMap; importjava.util.Stack; /** * http://blog.csdn.net ...

  8. 干货:4个小技巧助你搞定缺失、混乱的数据(附实例代码)

    导读:数据工作者经常会遇到各种状况,比如你收集到的数据并不像你期待的那样完整.干净.此前我们讲解了用OpenRefine搞定数据清洗,本文进一步探讨用pandas和NumPy插补缺失数据并将数据规范化 ...

  9. linux如何解除密码锁屏图案大全,手机锁屏图案(锁屏密码)忘记了怎么办?四种方法帮你轻松搞定...

    很多朋友可能为了追求新奇,对手机的各种功能都比较好奇,都想试一试.对于图案解锁这个功能也可能比较喜欢,但会不会就是刚刚设置完了就忘记了呢?那么手机锁屏图案(锁屏密码)忘记了怎么办?本文将为大家介绍四种 ...

  10. Java原来还可以这么学:如何搞定面试中必考的集合类

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...

最新文章

  1. vi/vim 中批量在行插入或删除指定字符
  2. 牛客NOIP2021提高组OI赛前模拟赛第一场T3——与巨(数学)
  3. Python(13)-函数,lambda语句
  4. 蚂蚁金服金融级容器引擎实践之路
  5. python列表切片和推导式思维导图_Python列表推导式使用
  6. Python打印到文件
  7. python基础--字符串单引号双引号和三引号
  8. 博客迁移至: http://www.cleocn.com
  9. 图:活动现场双屏管理系统V3-多线程抽奖版软件,完美升级收工!历时3个月,艰辛坎坷...
  10. unix操作系统图标大全
  11. 地图之美(地图制图)
  12. gis 数据框裁剪_【更新84篇】地理数据科学技术文章合集,欢迎大家点赞、在看、转发三连!...
  13. spurious wakeup虚假唤醒
  14. 考研数学笔记(更新中)
  15. python怎么算一元二次方程_Python实现求解一元二次方程的方法示例
  16. 三十、再问唐僧出生之迷
  17. c++之防卫式声明和模板
  18. 二维码相机遮罩层快速实现
  19. 工作方式需要主心骨、承载人
  20. chrome绿色版制作方法

热门文章

  1. iphone在jsp显示时间会NAN解决办法
  2. Yaksa让你抛弃Adapter和ViewHolder写RecyclerView
  3. StringBuilder 详解 (String系列之2)
  4. [转]在ASP.NET MVC5中实现具有服务器端过滤、排序和分页的GridView
  5. 恶魔的指纹---49幅由算法生成的七芒星图像
  6. Eclipse中添加Android系统jar包
  7. 《JQuery 能干点啥~》第四讲 html() 与 text()的赋值比较
  8. Illustrator 教程,如何在 Illustrator 中使用绘图模式?
  9. 如何在 macOS Monterey 中更改光标的颜色样式?
  10. 苹果Mac软件开发工具:Xcode 让开发者如虎添翼