目录

1.设计模式

2.饿汉模式

3.懒汉模式

4.线程安全与单例模式


1.设计模式

设计模式是什么?

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案

这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的

单例模式的作用就是保证某个类在程序中只存在唯一一份实例,不会创建出多个实例(之前学过的JDBC编程,DataSource这样的类就适合单例模式)

特点:

  • 1、单例类只能有一个实例
  • 2、单例类必须自己创建自己的唯一实例
  • 3、单例类必须给所有其他对象提供这一实例

单例模式分为"饿汉""懒汉"两种

2.饿汉模式

//饿汉模式
//此处保证只能创建一个实例
class Singleton{private static Singleton instance = new Singleton();//想要使用时,通过Singleton.getInstance()来获取!public static Singleton getInstance(){return instance;}//构造方法私有化,类外无法通过new来调用构造器创建实例!!private Singleton(){}
}
public class Thread {public static void main(String[] args) {Singleton singleton1 = Singleton.getInstance();Singleton singleton2 = Singleton.getInstance();System.out.println(singleton1==singleton2);}
}

构造器私有化之后是不能通过new来调用构造器实例化对象的

此处我们将这个实例设置成私有的,通过get方法来获取,并且将构造方法私有化,不能创建新实例,因此访问这个实例的时候,每次访问得到的是同一个引用

private static Singleton instance = new Singleton();

Singleton这个属性和实例无关,是和类相关的,java代码中的每个类在编译完成后都会得到.class文件,JVM运行时会加载这个文件读取其中的二进制指令,并在内存中构造对应的类对象(Singleton.class),这个过程就是类加载的过程

该模式是如何保证实例唯一呢

1.static修饰的实例instance,让当前实例的属性是类属性.在类加载阶段就被创建,一个类只加载一次,这个实例只创建唯一一份

2.构造方法私有化,类外无法再创建新的实例

这个单例模式的名称是"饿汉模式",这个名字的来由是与后面的"懒汉模式"相比较得出的,体现在:在类加载阶段,就直接创建出了实例,实在很靠前的阶段给人一种急迫的感觉,所以叫饿汉模式

3.懒汉模式

//懒汉模式
class SingletonLazy{private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if(instance == null){instance = new SingletonLazy();}return instance;}private SingletonLazy(){};
}
public class Thread {public static void main(String[] args) {SingletonLazy singleton3 = SingletonLazy.getInstance();SingletonLazy singleton4 = SingletonLazy.getInstance();System.out.println(singleton3==singleton4);}
}

懒汉模式的实例初始情况下是null,并非是在类加载时就创建出来了,而是第一次使用的时候才创建出来的,如果没有使用,那么就不创建了,单从效率来说是更好的选择

4.线程安全与单例模式

上述两种模式在多线程环境下调用getInstance是否是线程安全的呢?

先分析一下饿汉模式

 在饿汉模式中.多线程调用只涉及到了"读" 操作,我们知道多个线程只读一个变量是安全的,那么这个饿汉模式就是安全的

再看懒汉模式

这里涉及到了"读和写"两个操作,在多线程中调用,是不安全的

上述途中两个线程调用时,由于随机调度和指令重排序的特点,如果在t1线程还没有创建出实例,t2线程就调用,那么instance还是null,继续往下执行,那么t1t2会创建出两个实例,触发多次new操作了,就不满足单例模式这个应用场景的需求了!!导致了线程不安全

如何解决这个线程安全问题呢?

刚才的安全问题根本原因是读,比较,写这三个操作不是原子的,导致了t2读到的值可能是t1没来得及写的(脏读)

加锁肯定是解决线程安全问题的普适方法

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

这种加锁方式是不可取的!!这里只给new操作加锁了,那么t2还是可能读到t1没来得及写的数据,所以我们要给整个操作加锁! 保证读,比较,new,写这几个操作整体是原子的,正确的加锁方法如下

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

到这里t2读到的就是t1更新过的数据了,是一个非空值,不会触发if条件,也就不能new新的实例了,满足了单例模式的要求

但是我们每个线程调用get时都要加锁,加锁操作也是有开销的,频繁的加锁会降低效率.我们发现一旦有一个实例后,后续调用get时,instance肯定是非空的,就直接触发return,那么就不需要锁了!

所以我们再进行一个判定,如果对象还没创建就加锁,创建过了,就不加锁!

这种方式采用双校验锁机制,安全且在多线程情况下能保持高性能

getInstance() 的性能对应用程序很关键时使用

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

1处的if语句是判定是否需要加锁!

2处的if语句是判定是否要创建实例对象!

这两个连续相同的if语句在没有加锁的情况下是没有意义的,一个两个效果相同,但是中间加了锁,就可能引起线程阻塞,等到解锁之后,第一个if和第二个if之间对于计算机来说已经沧海桑田了!程序内部的状态,变量的值都可能发生很大改变

这样减少了不必要的加锁,但是还存在内存可见性问题!

假设有很多线程都来调用get,这个时候第一次调用是读内存,后续都是读寄存器/cache,那么就会有被优化的风险!

还有指令重排序引入的线程安全问题,new操作可以拆分为三个步骤

1.申请内存空间

2.调用构造方法,初始化对象

3.把空间地址赋给instance引用

编译器可能会为了提高程序效率将指令执行顺序调整,1不会被调整.23会被调整,单线程情况写123,132没有本质区别,最后都能new出实例对象,但是多线程情况下,t1如果执行132,执行到13后就被切换到t2来执行,此时t1的2还没有执行,instance仍然是一个null,t2却认为t1已经执行完3了,那么此处的引用就是非null的了,按照代码t2会直接返回一个instance引用,可能还会尝试使用引用中的属性,但是这是一个非法的实例对象,它并没有被构造完成!

解决内存可见性,指令重排序问题需要用到关键字--volatile

所以要使用volatile修饰instance!!这样就能解决内存可见性和指令重排序

线程安全的饿汉版单例模式:

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

单例模式及其线程安全问题相关推荐

  1. 单例模式与线程安全问题浅析

    近期看到到Struts1与Struts2的比較.说Struts1的控制器是单例的,线程不安全的:Struts2的多例的,不存在线程不安全的问题.之后又想到了之前自己用过的HttpHandler... ...

  2. spring单例模式与线程安全问题的解决方案

    一句话总结:1:spring容器创建对象的方式是单例的 2:spring单例模式的安全问题是使用ThreadLocal解决的 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行 ...

  3. 单例模式与线程安全问题

    文章目录 本次的学习总结是对Java中的单例设计模式的介绍与解析,对于作为一个开发者而言这个设计模式必须要了解透彻,基本素养吧. 文章目录 文章目录 前言 一.什么是设计模式? 二.什么是单例设计模式 ...

  4. java单例模式之线程安全问题

    单例的目的是为了保证运行时Singleton类只有唯一的一个实例,用于一些较大开销的操作. 饿汉式(没有线程安全问题): ' 由于使用static关键字进行了修饰,只能获取到一个对象,从而达到了单例, ...

  5. 双重检查锁实现单例模式的线程安全问题

    一.结论 双重校验锁的单例模式代码如下: public class Singleton { private static Singleton singleton; private Singleton( ...

  6. Java单例模式中的线程安全问题

    在Java中单例模式被分为懒汉式和饿汉式,饿汉式会在单例类加载时就创建实例而懒汉式则延迟实例化,在使用到单例实例的时候才实例化.在单线程的程序里两张方式没什么区别,多线程的话懒汉式会有线程安全问题.先 ...

  7. java 恶汉和懒汉_Java单例模式-懒汉式、恶汉式与线程安全问题

    Java的单例模式常见的分为懒汉式.饿汉式.静态内部类.枚举 通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数额控制并节约系统资源. 饿汉式: public cl ...

  8. 单例模式“双重检查锁定Double-Checked Locking”线程安全问题

    几篇合集. 1 单例模式"双重检查锁定Double-Checked Locking"线程安全问题 https://blog.csdn.net/wabiaozia/article/d ...

  9. struts2学习笔记--线程安全问题小结

    在说struts2的线程安全之前,先说一下,什么是线程安全?这是一个网友讲的, 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样 ...

最新文章

  1. python在哪里写代码比较适合-适合练习的10个Python项目,每个项目都不到500行代码...
  2. springMVC的使用
  3. android apk获取权限,Android apk 获取系统权限的方式
  4. 电机速度曲线规划2:S形速度曲线设计与实现
  5. excel不显示0_Excel2007:Excel表格中完整输入身份证号码的几种方法
  6. css盒子模型实验报告总结_前端知识点总结——盒子模型
  7. 五、hive-1.2.1安装
  8. 线性代数知识框架梳理
  9. 腾讯云神图·人脸识别(Face Recognition)常见问题的解答
  10. hd计算机技术,BD和HD的区别是什么?
  11. 计算机用户没有管理员权限,电脑没有管理员权限怎么办
  12. 喜报!字根科技牵手荣之联,提供错别字检测解决方案
  13. 基于标记的分水岭分割算法
  14. PDF中几个的空白页怎么删除?
  15. maven私服搭建,sonatype nexus
  16. 保护云端数据安全的方法,主要有哪几种?
  17. FileDetector-基于java开发的照片整理工具
  18. 对撞机 (Standard IO)
  19. Redis与Memorycache的区别
  20. GitHub官方出手,一针见血!Spring Boot趣味实战手册来袭(彩版)

热门文章

  1. Java代码防止被反编译的4中方法
  2. Python初学者必会的3款代码编辑器
  3. get和post、RequestMethod的GET和POST的使用总结
  4. 《Python自然语言处理》-ch1-语料库
  5. linux隐藏tomcat版本_Linux隐藏tomcat的版本信息
  6. 课题申报撰写方法详解,课题研究立项、科研成果
  7. 第一次直播完的感受,向小伙伴们致歉!
  8. vue scoped关键字
  9. VB.NET读取版本信息
  10. 解决问题过程记录: QuickTime初始化失败