当前位置: 首页 > news >正文

单例模式的相关知识

饿汉模式

package Thread;
class Singleton{private static Singleton instance = new Singleton();public static Singleton getInstance(){return instance;}private Singleton(){}
}public class demo1 {public static void main(String[] args) {Singleton S1 =Singleton.getInstance();Singleton S2 =Singleton.getInstance();System.out.println(S1==S2);
//        Singleton S3 =new Singleton();}
}

在这里插入图片描述

所谓的单例模式,就是指一个类的对象只能创建一个,上述代码中,Singleton这个类中,有一个static修饰的对象,这个对象就是一个类对象,具有类属性,由于类对象只有一个,也就是说,每个类的类对象是单例的,所以,类对象 的属性(static),也就是单例的了,这个对象创建的时间是在Singleton第一次被JVM使用的时候.
有了这个对象后,不能允许在类外面创建对象,因此,我们就需要将类的构造方法改为private修饰,这样,只要在类外面尝试用new来创建对象,而不是调用get方法,系统就会报错.
在这里插入图片描述
在这里插入图片描述
但是,这样并不保证就一定不可以在类外面创建多个不同的对象,"反射"这个操作就可以做到,但是做这个操作需要谨慎,滥用反射,会带来极大的风险,因此,我们并不推荐使用这个方法.

懒汉模式

package Thread;
class Singletonlazy{private static Singletonlazy instance = null;public static Singletonlazy getInstance(){if(instance==null){instance = new Singletonlazy();}return instance;}private Singletonlazy(){}
}
public class demo2 {public static void main(String[] args) {Singletonlazy S1 = Singletonlazy.getInstance();Singletonlazy S2 = Singletonlazy.getInstance();System.out.println(S1==S2);}
}

在这里插入图片描述
懒汉模式与饿汉模式最主要的区别就是,类对象创建的时间不同,懒汉模式并不是Singletonlazy类在第一次被加载的时候创建的,而是在get方法第一次被调用的时候才会创建

线程安全问题

饿汉模式的get方法
在这里插入图片描述
懒汉模式的get方法
在这里插入图片描述
从上述代码中可以看出,饿汉模式下的get方法只涉及到读取操作,并没有进行对变量的修改,而懒汉模式下的get方法,涉及对对象引用的修改,而且是存在if的判断条件,说明该修改操作不是一个原子级别的,综上所述,懒汉模式存在严重的线程安全问题,会导致出现两个线程都new出一个新的对象的情况,虽然系统最后会对后面new出来的对象进行清除,但是,创建两个对象所花费的资源和时间确是巨大的损失,所以,为了避免这样的损失,我们需要对该方法进行加锁

加锁

为了解决懒汉模式下的线程安全问题,我们尝试对get方法的修改操作进行加锁,注意,这里进行加锁的范围需要包含if的条件判断和修改的具体操作,需要将这两个步骤所在一起,才能解决线程安全问题
但是我们也需要注意,加锁也不能滥用,因为加锁也是很消耗资源和时间,能不加锁的情况就不要加锁,否则,会对代码的运行效率造成极大的影响
就比如这里我们锁需要加锁的代码,只有第一次调用的时候,才会对对象的引用进行修改,后续再次调用该方法的时候,也只是如饿汉模式下的方法一样,只是进行读操作,所以,我们要求,在加锁的时候,我们也要做到,只有在第一次具有线程安全问题的情况下,我们才进行加锁,其余情况下,就避免加锁带来 的额外的负担,那我们需要怎么做呢
再加一个if判断
在这里插入图片描述
先判断是否需要加锁,再判断是否真的需要加锁
第一个if是为了判断是否需要加锁,第二个if是判断是否需要创建新的对象
因为这里是多线程,在加锁的时候,可能涉及到阻塞的情况,阻塞时间的长短我们也不知道,因此,可能存在由于两个if之间的时间相隔过久,在这段时间里,可能会有其他线程将对象给修改掉的情况
但是这里的第二个线程一定能读取到第一个线程修改的值吗,这里就涉及到之前章节讲到的内存可见性

内存可见性

为了避免编译器优化导致的系统bug,我们需要给instance这个变量加上一个volatile,来确保代码的准确性
在这里插入图片描述

指令重排序

这里除了可能会有内存可见性的问题之外,也可能会有指令重排序导致的问题出现,什么是指令重排序呢,举个例子
假如我们完成一个工作的步骤是123,经过编译器优化之后,步骤就变为了132,甚至321.这样的优化的好处和内存可见性的优化一样,都可以大幅度提高程序运行的效率,但是这如果只是在单线程的情况下还好,但是在多线程的情况下了,就有很大可能会引发线程安全问题,再举个例子
我们想要new一个instance对象出来,需要分为三个步骤
1.创建出内存空间,得到内存地址
2.再空间上调用构造方法,对对象进行初始化
3.把内存地址幅值给Instance引用
如果是单线程的情况下,23可以互调,不会有太大的影响
但是如果发生在多线程的情况下,此时还没来得及给instance初始化,就调度给其他线程了,第二个线程在执行的时候,判定instance!=null,于是就把instance给返回出去了,并且后续可能还会涉及掉用instance中的方法,而instance 现在只是一具空壳,并没有被初始化,这就可能会造成严重的问题
那么我们怎么解决这个问题呢,这里我们也可以利用volatile来解决问题.
给instance加上volatile之后,就不会产生指令重排序了

http://www.lryc.cn/news/143519.html

相关文章:

  • vue问题相关记录
  • skywalking服务部署
  • 【uni-app】压缩图片并添加水印
  • 《每天十分钟》-红宝书第4版-变量、作用域与内存
  • NFTScan | 08.21~08.27 NFT 市场热点汇总
  • 【Java 中级】一文精通 Spring MVC - 数据验证(七)
  • css奇数偶数选择器
  • 【算法】双指针求解盛最多水的容器
  • 浅析SAS协议:设备接入与探测
  • RISC-V IOPMP实际用例-Andes SoC‘s Rapid-k模型
  • 【高阶数据结构】哈希表详解
  • C#与西门子PLC1500的ModbusTcp服务器通信4--搭建ModbusTcp客户端
  • 性能调优篇 二、Jvm监控及诊断工具-命令行篇
  • Fooocus启动时modules报错的解决方法
  • RSA私钥解密操作
  • 数据库基本知识
  • 使用Redis统计网站的UV/DAU
  • 【python】报错:ImportError: DLL load failed: 找不到指定的模块 的详细解决办法
  • SemrushBot蜘蛛爬虫屏蔽方式
  • 6 ssh面密登录
  • 基于微信小程序的汽车租赁系统的设计与实现ljx7y
  • 优化学习体验的在线考试系统
  • 1267. 统计参与通信的服务器
  • 【考研数学】矩阵、向量与线性方程组解的关系梳理与讨论
  • 打造个人的NAS云存储-通过Nextcloud搭建私有云盘实现公网远程访问
  • FFI绕过disable_functions
  • 53 个 CSS 特效 2
  • ubuntu学习(六)----文件编程实现cp指令
  • wireshark过滤器的使用
  • Zookeeper 脑裂问题