单例设计模式,意味着整个系统中只能存在一个实例,比方说像日志对象这种。我们常说的有饿汉式和懒汉式这两种模式来创建单例对象,今天就拓展一下思维,多看几种。

首先我们若是想一个类只有一个对象,那肯定先要私有化构造器,断了在其它的类中使用构造器创建实例的念头。其它的类中不能创建,我们就只能在类中自己创建一个私有实例,另外还要提供一个共有的方法使其它对象获取到实例。所以,第一版出现了。

1 【饿汉式 V1】

在类加载的时候就创建实例

1br/>2
3
4
5
6
7
8
9
10
11
@ThreadSafe
public class SingletonExample2 {
// 私有化构造器
private SingletonExample2(){}
// 提供一个实例
private static SingletonExample2 instance = new SingletonExample2();
// 提供共有的方法返回实例
public static SingletonExample2 getInstance(){
return instance;
}
}

不要忘了在多线程环境中还有关注线程是否安全,我这里都会打上注解,@ThreadSafe 表示线程安全,@NotThreadSafe 表示线程不安全。

上面这种方式就是比较简单的,也是最容易想到的方式,就有一个缺点,若是不使用这个对象,那就有点浪费资源了,这个对象不一定会被使用,但是我们已经创建好了。

2 【饿汉式 V2】

这种方式是借助于 "静态代码块只会被加载一次" 来实现单例的创建,很简单,也很好理解,问题和饿汉式一样,不一定就会使用到这个对象,所以可能会出现浪费资源的情况。

1br/>2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ThreadSafe
public class SingletonExample6 {
// 私有化构造器
private SingletonExample6(){}

private static SingletonExample6 instance = null;static {instance = new SingletonExample6();
}
// 提供共有的方法返回实例
public static SingletonExample6 getInstance(){return instance;
}

}

3 【懒汉式 V1】

在对象使用的时候才创建实例

1br/>2
3
4
5
6
7
8
9
10
11
12
13
14
@NotThreadSafe
public class SingletonExample1 {
// 私有化构造器
private SingletonExample1(){}
// 提供一个实例
private static SingletonExample1 instance = null;
// 提供共有的方法返回实例
public static SingletonExample1 getInstance(){
if(instance == null){
return new SingletonExample1();
}
return instance;
}
}

这种方式在单线程的时候是没有问题的,但是在多线程时就会出现问题,假如线程 A 进入 if 之后暂停执行,此时又来一个线程 B 还是可以进入 if 并返回一个实例,此时 A 再次获得执行时,返回的是另一个实例了。

4 【懒汉式 V2】

在共有方法上添加 synchronized 关键字,同步该方法。可行,但是不推荐使用,因为 synchronized 修饰方法之后,在同一时刻只能有一个线程执行该方法,一旦有线程获得方法,其它线程需要等待,这样会浪费大量时间,系统运行效率降低。

1br/>2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ThreadSafe
@NotRecommend
public class SingletonExample3 {
// 私有化构造器
private SingletonExample3(){}
// 提供一个实例
private static SingletonExample3 instance = null;
// 提供共有的方法返回实例
public static synchronized SingletonExample3 getInstance(){
if(instance == null){
return new SingletonExample3();
}
return instance;
}
}

5 【懒汉式 V3】

这种方式使用双重检测 + 防止指令重排的方式来保证线程安全,首先需要注意的是在 getInstance 方法中,我们需要双层检测并使用同步代码块将创建对象的过程同步起来。

1br/>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@NotThreadSafe
public class SingletonExample4 {
// 私有化构造器
private SingletonExample4(){}
// 提供一个实例
private static SingletonExample4 instance = null;
// 提供共有的方法返回实例
public static SingletonExample4 getInstance(){
// 线程 B 判断,发现 instance 不为空,直接返回,而实际上 instance 还没有初始化。
if(instance == null){ // 双重检测机制
synchronized (SingletonExample4.class) { // 同步锁
if(instance == null){
// 线程 A 执行到重排后的指令 3 ,此时 instance 已经有地址值了。但是没有初始化
return new SingletonExample4(); // 这里是重点!!
}
}
}
return instance;
}
}

因为在 new SingletonExample4() 的过程中,并不是一个原子操作,是可以进一步拆分为:

1、分配对象内存空间

memory = allocate()

2、初始化对象

initInstance()

3、设置 instance 指向刚分配的内存

instance = memory

在多线程的情况下,上面 3 个指令会存在指令重排序的情况。【JVM 和 CPU 指令优化】重排后的结果可能为:

memory = allocate()

instance = memory

initInstance()

此时可能会存在线程 A 在内层 if 执行到指令重排后的第 3 步,但并未初始化,只是存在了地址值,线程 B 在外层 if 判断时,会直接 return 实例,而这个实例是一个只有地址值而没有被初始化的实例。沈阳哪里看胃权威www.120sdwc.com

为了防止指令重排带来的问题呢,我们就可以使用 volatile 关键字防止指令重排。这样就是线程安全的了。只需在上一版的基础上使用 volatile 修饰 instance 实例即可。

volatile 的语义就是添加内存屏障和防止指令重排,这在前面已经分析过了。

private static volatile SingletonExample4 instance = null;

6 【使用枚举类实现单例模式】

这是推荐使用的方法,因为它比懒汉式的线程安全更容易保证,比饿汉式的性能高,它只有在调用的时候才实例对象。

1br/>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@ThreadSafe
@Recommend
public class SingletonSpecial {
private SingletonSpecial(){}

public static SingletonSpecial getInstance(){return Singleton.INSTANCE.getInstance();
}private enum Singleton{INSTANCE;// public static final Singleton INSTANCE;private SingletonSpecial singleton;// JVM 来保证这个构造方法只会调用一次Singleton(){singleton = new SingletonSpecial();}public SingletonSpecial getInstance(){return singleton;}
}

}

7 【使用静态内部类】

这种方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会加载 SingletonInstance 类,从而完成 Singleton 的实例化。

使用 static final 修饰之后 JVM 就会保证 instance 只会初始化一次且不会改变。

1br/>2
3
4
5
6
7
8
9
10
11
12
13
14
@ThreadSafe
@Recommend
public class SingletonExample7 {

private SingletonExample7(){}private static class SingletonInstance{private static final SingletonExample7 instance = new SingletonExample7();
}public static SingletonExample7 getInstance(){return SingletonInstance.instance;
}

}

总结一下,今天主要说了单例模式的实现,并且在这中间,复习了一下前面说的线程安全的应用。若是对线程安全的原理以及实现有不懂的可以回头看看前面几篇文章。

转载于:https://blog.51cto.com/14236847/2361729

彻底搞懂单例模式如何安全的实现相关推荐

  1. 【学亮说】Java实现单例模式的8种方式(你真的搞懂单例模式了吗?)

    第一种:饿汉式单例模式 package com.zhangxueliang.dp.sigleton;/*** 饿汉式单例模式* * @Author:Zhang Xueliang* * @Date:20 ...

  2. 一个案例搞懂工厂模式和单例模式

    一个案例搞懂工厂模式和单例模式 1 单例模式 一个对象只有一个实例 单例类必须自己创建自己的唯一实例. 单例类必须给所有其他对象提供这一实例. 注意:所有的单例模式,应当使其构造方法私有化. 1.1 ...

  3. factorybean 代理类不能按照类型注入_彻底搞懂依赖注入(一)Bean实例创建过程

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 上一章介绍了Bean的加载过程(IOC初始化过程),加载完成后,紧接着就要用到它的依赖注入 ...

  4. 一文搞懂MySQL数据库分库分表

    如果数据量过大,大家一般会分库分表.分库需要注意的内容比较少,但分表需要注意的内容就多了. 工作这几年没遇过数据量特别大的业务,那些过亿的数据,因为索引设置合理,单表性能没有影响,所以实战中一直没用过 ...

  5. zhs16gbk对应mysql_[Oracle] 彻底搞懂Oracle字符集

    基本概念字符集(Character set):是一个系统支持的所有抽象字符的集合.字符是各种文字和符号的总称,包括各国家文字.标点符号.图形符号.数字等.常见的字符集有ASCII,ZHS16GB231 ...

  6. 面试必备|带你彻底搞懂Python生成器

    2019年人工智能系统学: https://edu.csdn.net/topic/ai30?utm_source=ai100_bw 作者 | Rocky0429 转载自 Python空间(ID:Dev ...

  7. 20分钟教你搞懂Git!

    Git 是最流行的版本管理工具,也是程序员必备的技能之一.本文就来教你 20 分钟搞懂 Git! 以下为译文: 尽管每天你都会用到Git,但也有可能搞不懂它的工作原理.为什么Git可以管理版本?基本命 ...

  8. 搞懂机器学习的常用评价指标!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:陈安东,湖南大学,Datawhale成员 我与评价指标的首次交锋是 ...

  9. 一文搞懂结构体的定义及实际使用

    大家好,我是无际,无实战不理论. 今天给大家讲一下结构体. 大家可能很好奇,为什么这种文章教程泛滥了,我还要拿出来讲. 相信无际的铁粉都知道,我分享的干货和经验出发点是实际产品应用. 脱离产品你所学的 ...

最新文章

  1. 科大讯飞2019年创下历史最佳业绩,员工涨薪27%,营收来源妥妥「安徽之光」
  2. shell脚本监控系统负载、CPU和内存使用情况
  3. oracle表空间最大30G?如果一张表超过30G怎么办
  4. Unity3D 预设打包的注意事项
  5. 怎么将string list 转成有特殊字符分开字符串
  6. osg中实现HUDAxis功能
  7. Python:Sklearn概述
  8. Android下写一个永远不会被KILL掉的进程/服务
  9. appium文件夹下无.bin文件_手把手教你Win10应用商店文件夹无权限访问怎么处理
  10. .NET Core 3.0带来桌面支持(Windows Only)
  11. 过新版狗php一句话,整理的最新WebSHell (php過狗一句話,過狗菜刀,2016過狗一句話,2016php免殺一句話)...
  12. Metatable让我从心认知了Lua(相知篇)
  13. 【Luogu P1878】舞蹈课
  14. 数组除重和应用随机数进行随机点名
  15. 定义一个鸭子的类java_2019-02-11——Java 鸭子模型
  16. Win10应用小技巧
  17. 第69天-内网安全-域横向 CobaltStrikeSPNRDP
  18. 车辆姿态角(Euler角)Pitch、Yaw、Roll 的设定
  19. R语言可视化进阶-高级点图、气泡图、动态图、图形叠加与相关图
  20. angular4 - 思维导图(xmind)

热门文章

  1. ae制作小球轨迹运动_关于3D建筑漫游动画制作流程及技术详解
  2. 新浪微博php7升级实践,PHP7线上system time飙高问题
  3. 软件测试52讲-安全第一:渗透测试
  4. c语言利用循环结构解决密码转换,C语言课件第六章循环结构.ppt
  5. 10分钟学会数据库压力测试
  6. 卑微测试员自述:入职新公司一个月,就让我做自动化测试?!
  7. html5中加入音频,在H5场景中插入自定义音频和视频(任意画面)
  8. centos7本地安装mysql_centos7安装mysql
  9. python分割压缩_Python读取分割压缩TXT文本文件实例
  10. 动态展开所有_动态演示立方体的展开,并且显示11种展开图——GeoGebra制作教程...