单例模式

  • 定义
  • 单例模式的实现
    • 饿汉模式
    • 懒汉模式
    • 线程安全问题分析:
    • 如何解决线程安全问题??
      • 关键点总结

定义

单例模式,是一种常见的"设计模式"

设计模式: 设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性

单例模式,场景: 代码中,有些概念,不应该存在多个实例,此时应该使用单例模式来解决
例: MySQL JDBC中,第一步就是创建一个 DataSourse 对象,DataSourse 对象,在一个程序中只有一个实例,不应该实例化多份DataSourse 对象
可以用单例模式来解决这种场景,保证指定的类只有一个实例 (若尝试创建多个实例,直接编译就会报错)

单例模式的实现

饿汉模式

类加载的同时,创建实例
(只要类被加载,就会立刻实例化 Singleton 实例)

代码:

public class ThreadDemo22 {/**  饿汉模式 单例实现*  "饿" —— 只要类被加载,实例就会立刻被创建 (实例创建的时机比较早)* */static class Singleton{// 把构造方法变成私有的,此时在该类的外部就无法 new 这个类的实例了private Singleton(){}// 再来创建一个 static 的成员,表示 Singleton 类唯一的实例// static 成员 和类相关,和实例是无关的// 类在内存中只有一份,static 成员也只有一份private static Singleton instance = new Singleton();public static Singleton getInstance(){return instance;}}public static void main(String[] args) {// 此处 new 可以,是因为 Singleton 是 ThreadDemo22 的内部类,// ThreadDemo 是可以访问 内部类的 private 成员的Singleton s = new Singleton();// 此处的 getInstance 就是获取该类实例的唯一方式,不应该使用其他方式来创建实例Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);}
}

输出结果:

懒汉模式

当类被加载的时候,不会立刻实例化
等到第一次使用这个实例的时候,再实例化

public class ThreadDemo23 {/** 懒汉模式* */static class Singleton{private Singleton() {}// 类加载的时候,没有立刻实例化// 第一次调用 getInstance 时,才真正的实例化private static Singleton instance = null;public static Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}}
}

类加载的时候,没有立刻实例化;第一次调用 getInstance 时,才真正的实例化

若代码,一直没有调用 getInstance,此时实例化的过程也就被省略掉了 —— 延时加载
一般认为,“懒汉模式” 比 “饿汉模式” 的效率更高~
原因: 懒汉模式有很大的可能是 “实例是用不到”,此时就节省了实例化的开销

线程安全问题分析:

思考: “饿汉模式” 和 “懒汉模式”,哪个是线程安全的???
(线程安全:假设多个线程并发的调用 getInstance 方法,是否会导致逻辑错误)

啥样的情况会导致线程不安全???

在线程安全问题,我们提到有以下原因:

  • 线程是抢占式执行的
  • 修改操作不是原子的
  • 多个线程尝试修改同一个变量(单例模式常出现)
  • 内存可见性
  • 指令重排序

饿汉模式—线程安全;
懒汉模式—线程不安全

分析:
1.饿汉模式:
实例化时机是在类加载的时候,而类加载只有一次机会,不可能并发执行
当多线程并发的调用 getInstance 时,由于 getInstance 里只做了一件事:读取 instance 实例的地址,相当于多个线程同时读取同一个变量;因此,饿汉模式是线程安全的

2.懒汉模式:
多线程同时调用 getInstance 时,getInstance 中做了四件事:①读取 instance 的内容;②判断是否为null;③若 instance 为 null,就 new 实例;④返回实例的地址
当 new 实例的时候,就会修改 instance 的值

画图分析:


懒汉模式,后续调用 getInstance 都不会触发线程安全问题,只有在第一次实例化的时候,多线程并发调用 getInstance 时,会有线程不安全问题的风险

如何解决线程安全问题??

那么,如何改进 懒汉模式,让代码变成线程安全的???

方法1— 加锁 synchronized

  • 改法1

这样写,读取判断操作,和new 修改操作 仍然不是原子的,故这样修改不可行!!

private static Singleton instance = null;public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){instance = new Singleton();}}return instance;
}
  • 改法2

这么加是可以保证原子性的

private static Singleton instance = null;public static Singleton getInstance(){synchronized (Singleton.class){if(instance == null){instance = new Singleton();}}return instance;
}

上述改法,虽然解决了线程不安全的问题,但仍然会问题 — 效率问题,

画图分析:

  • 改法3
private static Singleton instance = null;synchronized public static Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;
}

画图和改法2 差不多,只不过 return 操作是在释放锁内部来完成的
由于 return 只是在读,所以这个操作放到锁里边或者锁外边不影响结果

虽然改法2 和 改法3 都可行,但是改法2 的锁粒度更小,改法3 的锁粒度更大
锁的粒度: 锁中包含的代码越多,就认为锁粒度越大
一般,我们希望锁的粒度小一点更好,因为锁的粒度越大,说明这段代码的并发能力就越受限

方法2 — 双重 if

由于加锁是为了避免第一次创建实例时线程不安全,后面在进行加锁解锁操作都只会降低性能,所以外层再添加 if 判断,当发现其为空时才加锁,否则直接返回已经创建好的实例对象,减少了加锁解锁的次数,从而提高性能

private static Singleton instance = null;public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){if(instance == null){instance = new Singleton();}}}return instance;
}

画图分析:

  • 实例化之前:

此处有多个读操作,可能会被编译器优化:只有第一次读,才从内存中读,后续的读就是从CPU中读取寄存器(上次读到的结果)
这样就可能导致线程1 修改之后,线程2 没有读到最新的值

  • 实例化之后:

为了改进上述可能出现的编译器优化的问题,再添加 volatile

方法3 — volatile

private volatile static Singleton instance = null;public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class){if(instance == null){instance = new Singleton();}}}return instance;
}

懒汉模式的最终优化结果:

static class Singleton {private Singleton() {}//创建static成员变量,标识Singleton类的唯一实例,为避免内存可见性问题,添加volatileprivate volatile static Singleton instance = null;public static Singleton getInstance() {// 加锁是为了避免第一个创建实例时线程不安全,后面在进行加锁解锁操作都只会降低性能if (instance == null) {//如果为空,说明实例还未存在(即第一次使用),则创建实例//加锁,确保判断为空和 new对象两个操作 成为原子操作synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

关键点总结

1.加锁 — 保证线程安全
2.双重 if — 保证效率
3.volatile — 避免内存可见性引发的问题

以上三点缺一不可

单例模式 (饿汉、懒汉)相关推荐

  1. Singleton模式(单例模式) 饿汉式和懒汉式

    目的:整个应用中有且只有一个实例,所有指向该类型实例的引用都指向这个实例. 好比一个国家就只有一个皇帝(XXX),此时每个人叫的"皇帝"都是指叫的XXX本人; 常见单例模式类型: ...

  2. 单例模式——饿汉式和懒汉式

    单例模式:一个类只允许存在唯一的对象,并提供它的访问方式 创建思路: 1.禁止在类的外部创建对象:私有化构造函数 2.在类的内部提供唯一的对象:静态成员变量 3.提供访问唯一对象的方法:静态成员函数 ...

  3. Java-Day12 面向对象的三大特征之封装、继承,单例模式(饿汉式、懒汉式)、方法重写 (覆盖)、注解 (annotation)、super关键字、对象的创建流程超详细

    目录 1. 面向对象的三大特征之封装 1.1 封装的含义 1.2 封装的目的 1.3 封装的优点 1.4 封装的实现过程 1.5 拓展知识:JavaBean 2. 面向对象的三大特征之继承 2.1 继 ...

  4. 单例模式饿汉式与懒汉式详解

    单例模式饿汉式与懒汉式详解 步骤: 私有构造方法,使得在类的外部不能调用此方法,限制产生多个对象 类初始化时,区分饿汉式与懒汉式的区别 对外部提供调用方法,将创建的对象返回,只能通过类来调用 饿汉式: ...

  5. 单例模式饿汉模式与懒汉模式

    目录 1.什么是单例模式 2.为什么需要单例模式? 3.如何实现单例模式 3.1饿汉方式 3.2懒汉模式 1.什么是单例模式 单例模式是一种设计模式,单例模式能保证某个类在程序中只存在唯一一份实例, ...

  6. 单例模式——饿汉模式懒汉模式

    目录 一.什么是单例模式? 二.单例模式的应用场景 三.两种典型的方式实现单例模式 1.饿汉模式 2.懒汉模式 3.理解懒汉模式和饿汉模式 四.单例模式和线程的关系 1.饿汉模式是否线程安全? 2.懒 ...

  7. DBCP使用BasicdataSource连接(两种单例模式-----饿汉和懒汉模式)

    DBCP使用BasicDataSource连接 BasicDataSource实现DataSource的接口,可以进行简单的数据库连接 第一种:懒汉模式:顾名思义,"懒",只有在调 ...

  8. Java中单例模式----饿汉式(HungrySingLeton)和懒汉式(LazySingLeton)

    一. 单例模式概述 单例模式(Singleton),也叫单子模式,是一种常用的设计模式.在应用这个模式时,单例对象的类必须保证只有一个实例存在.许多时候,整个系统只需要拥有一个的全局对象,这样有利于我 ...

  9. 单例模式饿汉式/懒汉式的区别

    単例设计模式(Singleton Pattern): 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法. 如果我们要 ...

最新文章

  1. SPFA-DFS P3385 模板 判断负环===vector为啥过不了?
  2. 决策树ID3、决策树C4.5、决策树CART、CART树的生成、树的剪枝、从ID3到CART、从决策树生成规则、决策树优缺点
  3. 如何从开发人员走向架构师
  4. mysql 取出20条数据_“取出数据表中第10条到第20条记录”的sql语句+select top 使用方法...
  5. jquery load使用方法
  6. 关于优酷开放SDK之onPrepareListener
  7. 第五章(1)Libgdx应用框架之生命周期
  8. PDF N-Up Maker:一个把PDF转成小册子或者把多个页面放到一个页面上的工具(免费,免Acrobat,命令行模式)...
  9. URAL1815 Farm in San Andreas(费马点,圆圆相交)
  10. CSS页面重构“鑫三无准则”之“无图片”准则——张鑫旭
  11. 编写算法判别给定二叉树是否为完全二叉树_推荐一位实力超强的平安前端算法大佬:瓶子君...
  12. 学习 Message(11): 测试 TWMMouse 结构相关的鼠标消息
  13. tensorflow学习笔记(3):使用tf.data API导入数据
  14. 使用css弹性盒子模型
  15. 普通二维码扫码进入小程序(详细教程——从配置到开发)
  16. 晶振 负载电容 匹配电容
  17. 【基于物理的渲染(PBR)白皮书】(四)法线分布函数相关总结
  18. win7dns网络服务器未响应,Win7系统DNS服务器未响应问题的解决方法
  19. 1-2 二十四点 (20 分)【Csp认证真题】
  20. 毕业一年,工作一年,有收获,有失去,有遗憾,但仍一往无前 (征文)

热门文章

  1. 深度学习优化算法:Adam算法
  2. 什么是有效的电子档案管理
  3. 借助百度云平台人脸识别sdk完成网页人脸识别登录demo
  4. 什么是 photoshop 中最重要、最不可缺少的功能?我认为是“通道”吧!
  5. Redis 定长队列的探索和实践
  6. 如何判断List是否为空
  7. IT观察站:向过去致敬 3张图解释什么是做工
  8. 微服务架构开发实战:API网关意义和常见API网关的实现方式
  9. 美丽的英文诗句【1】
  10. 各种视频监控上墙方案的比较