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

多线程--单例模式and工厂模式

一.什么是设计模式

设计模式好⽐象棋中的"棋谱".红⽅当头炮, ⿊⽅⻢来跳.针对红⽅的⼀些⾛法,⿊⽅应招的时候有⼀ 些固定的套路.按照套路来⾛局势就不会吃亏.

 软件开发中也有很多常⻅的"问题场景".针对这些问题场景,⼤佬们总结出了⼀些固定的套路.按照这 个套路来实现代码,也不会吃亏.

设计模式与框架(spring...)的区别 

框架是一种硬性要求 就是必须服从框架的规范

设计模式是软性要求 灵活度高 根据需求可以调整

二. 什么是单例模式

单例模式是主流23种设计模式的一种具体是哪23种可以AI一下

单例模式是一种典型的模式,也是比较简单的模式,日常开发中更容易用到的模式

单例模式能保证某个类在程序中只存在唯⼀⼀份实例,不能创建多个实例

比如在JDBC中的DateSource就只能创建一个实例

毕竟数据库的数据大小动辄上百的G 多创建一个实例多的存储开销可不是一般的大,更影响性能,数据库的数据就那些创建多个实例也没必要

 单例模式具体的实现⽅式有很多,常见的还是饿汉模式和懒汉模式

饿汉模式:

class SingletonHungry {                                                                                         //静态成员的初始化是在加载的阶段触发的 类加载往往就是在程序时候就会触发                                                                       private static SingletonHungry instance = new SingletonHungry();//如果带参数 下面构造方法 也要调用带有参数的                    public static SingletonHungry getInstance() {                                                               return instance;//统一后面用这个方法来获取实例                                                                        //由于这里是读取操作不涉及线程安全                                                                             }//.........类的内容                                                                                                           //私人构造方法                                                                                                    private SingletonHungry() {                                                                                 //点睛之笔 不让在外部构造实例                                                                                        }                                                                                                           
}                                                                                                               
public class Demo26 {                                                                                           public static void main(String[] args) {                                                                    SingletonHungry t1 = SingletonHungry.getInstance();                                                     SingletonHungry t2 = SingletonHungry.getInstance();                                                     System.out.println(t1 == t2);                                                                           //SingletonHungry t3 = new SingletonHungry();报错                                                         }                                                                                                           
}                                                                                                               

 

饿汉模式顾名思义就是饥饿迫切的在类中构建一个实例(static静态变量,在JVM进行类加载的时候就创建) 由于是单例模式,类中就已经实例出来类的对象以后就通过类名来调用它的实例,构造方法修改为private不让外部再创建实例,由于获取实例是读取操作是原子的所以是天然的线程安全的

懒汉模式:

懒汉模式在类加载的时候不创建实例,在第一次使用的时候创建,如果一直不适用也就没必要创建了,省去了一些内存性能的开销

假设一个小说(千万字)肯定是只把一部分展示出来,后续如果用户翻页,随着翻页,随时就在后续数据,如果全部加载出来,可能会导致程序崩溃

打开编译器的把所有内容,都是从文件加载到内存中,再显示

class SingletonLazy {private static SingletonLazy instance = null;//1public static SingletonLazy getInstance() {//这样有线程安全问题//在多个线程下由于这个操作不是原子的 会由于线程的随机调度引发创建实例覆盖的问题if (instance == null) {//防止new多个实例instance = new SingletonLazy();//创造时机是在第一次使用的时候 而不是在程序启动的时候(节省空间资源更推荐)}return instance;}
}

上述懒汉方式由于if(判断)读操作 和new实例(修改操作)涉及一个读操作一个写操作不是原子的在单线程中不涉及线程安全,在多线程中线程安全问题就很明显,如下↓

第一个线程new出来对象,由操作系统的调度器,调度到线程二开始执行new操作,随着第二个线程的覆盖操作,第一个new出来的对象随后会被JVM的垃圾回收 回收掉 

两次new操作在数据很少的时候其实都无所谓,但是还是拿JDBC中new DataSource的时候数据内存很大可能new一个就要十分钟 如果出现两次new操作就会大大削减性能,甚至还会导致服务器崩溃

所以要解决

解决不是原子性问题肯定是要加锁

加上synchronized可以改善这⾥的线程安全问题.

class SingletonLazy {private static SingletonLazy instance = null;private static Object locker = new Object();//锁对象public static SingletonLazy getInstance() {//为了解决原子性问题就要加锁/*** 引入锁之后 后执行的线程在加锁的位置阻塞 阻塞到前一个线程解锁* 当后一个线程进入条件的时候 前一个线程已经修改完毕* isnstance不会再为null 就不会后续的new* 两个线程两把锁(这里只有一把锁) 无法构成请求和保持 不会死锁*/synchronized (locker) {//也可以再函数首部加锁if (instance == null) {instance = new SingletonLazy();}}/*** 实例创建好后 后去再调用该方法 都是直接return 如果只是进行if(判定) + 读取return 就不涉及到线程安全的问题了* 但是每次调用上述方法 都会触发依次加锁的操作 虽然不涉及安全问题了* 多线程的情况下 这里的加锁 就会相互阻塞 影响程序的执行效率  */return instance;}

引入锁之后,后执行的线程就会在加锁的位置阻塞,一直阻塞到前一个线程解锁

当后一个线程进入条件的时候,前一个线程已经修改完毕 instance不再为null就不会进行后续的new操作

但是加入锁之后就引入了一个新的问题:

实例创建好后 后去再调用该方法 都是直接return

如果只是进行if(判定) + 读取return(纯读取的操作) 就不涉及到线程安全的问题了 但是每次调用上述方法 都会触发依次加锁的操作

虽然不涉及安全问题了 多线程的情况下 这里的加锁 就会相互阻塞 影响程序的执行效率 所以要改善效率问题

 getInstance可以这样改

public static SingletonLazy getInstance() {if (instance == null) {//判断是否需要加锁synchronized (locker) {if (instance == null) {//判断是否需要new对象 两个if在单线程中执行流只有一个 if判定结果一样//在多线程中其他线程可能就会把if中的instance变量给修改了也倒是两次的if结果的结论不同instance = new SingletonLazy();}}}return instance;}

 在锁的外面再加一层if条件的判断来判断是否需要加锁,两个if执行的意义不一样可看代码注释

还有一些其他问题:

内存可见性,就不如再t1线程在读取instance的时候,t2线程进行修改,由于编译器的内存优化的逻辑非常复杂保险起见还是在instance上加一个volatile关键字,从根本上杜绝内存可见性问题

还有一个更关键的问题:指令重排序

指令重排序也是编译器优化的一种体现形式,编译器会在逻辑不变的前提下,调整你的代码顺序表来达到提升性能的效果

编译器优化往往不只是javacode自己的工作,通常是javac和JVM配和的效果(甚至是操作系统也要参与配合)

上述这个代码的机器指令中

instance = new SingletonLazy();
三步骤:1.申请内存空间 2.在空间上构造对象(初始化) 3.内存空间的首地址赋值给引用变量
* 正常来说按照123的顺序执行命令
* 但是在指令重排序的情况下会成为132这样的顺序 单线程123 132都一样 多线程可能有bug
* 可能会拿着未初始化的实例来进行操作

 如下图指令重排序的bug

 

解决的话还是在instance 上加上volatile关键字预防指令重排序问题 

    private static volatile SingletonLazy instance = null;
volatile的功能两方面
* 1.确保每次读取操作都是读内存
* 2.关于变量的读取和修改操作不会引起指令的重排序(主要)

三.工厂模式

我们先来看一个场景

在一个平面直角坐标系中描述一个点的位置,可以通过x,y两点的坐标直接确立,也可以通过极坐标的半径r和α角来确立.

class Point {public Point(double x, double y) {//构造点的坐标}public Point(double r, double a) {//构造点的极坐标}}
上述两个构造方法是在平面直角坐标系中确定一个点
第一个方法是直角坐标系 第二个方法是极坐标系
两个方法构成冲突也不能重载(overload)

解决方法就是利用工厂方法

利用一个工厂类来提供工厂方法

class Point {}class PointFactory {//工厂方法public static Point makePointByXY(double x, double y) {Point p = new Point();//通过x 和 y给p进行属性设置return p;//返回构造好的Point对象}public static Point makePointByRA(double r, double a) {Point p = new Point();//通过r 和 a给p进行属性设置return p;}
}

工厂方法的核心,通过静态方法,把构造对象new的过程各种属性初始化的过程给封装起来
提供多组静态方法 实现不同情况的构造

 

使用就可以这样使用 通过拿Point类来接受工厂方法的返回值也就类似于Point 来new实例了

public class Demo {public static void main(String[] args) {Point p = PointFactory.makePointByXY(10, 20);}
}

上述只是一个工厂模式使用的案例:解决构造方法冲突 还有其他的作用,如下:

1.解耦对象的创建和使用

2.提高代码的可维护性和可扩展性

3.便于代码的重复使用

4.便于对象的管理和控制

 当然也有缺点:复杂性增加,还需要权衡选择不要过度依赖,造成代码冗余

 

http://www.dtcms.com/a/278920.html

相关文章:

  • 研究人员利用提示注入漏洞绕过Meta的Llama防火墙防护
  • 隐藏源IP的核心方案与高防实践
  • 缺乏项目进度验收标准,如何建立明确标准
  • 基于STM32的智能抽水灌溉系统设计(蓝牙版)
  • 几种上传ipa到app store的工具
  • C#/.NET/.NET Core技术前沿周刊 | 第 46 期(2025年7.7-7.13)
  • 当前(2024-07-14)视频插帧(VFI)方向的 SOTA 基本被三篇顶会工作占据,按“精度-速度-感知质量”三条线总结如下,供你快速定位最新范式
  • 文本生成视频的主要开源模型
  • Redis客户端编程
  • python之Scikit-learn章节
  • 【日常技能】excel的vlookup 匹配#N/A
  • 《大数据技术原理与应用》实验报告二 熟悉常用的HDFS操作
  • 【王树森推荐系统】聚类召回
  • git 访问 github
  • 多用户图书管理系统
  • 张艺兴探班RED女团一周年舞台,见证21岁的梦想落地生根
  • sqli-labs靶场通关笔记:第11-16关 POST请求注入
  • 文献查找任务及其方法
  • 车载诊断框架 --- 车载诊断GuideLine
  • 【t检验】用奶茶店排队案例解释
  • urlencode、html实体编码、unicode
  • ChatDev 简易指导文档
  • Spring Boot Cucumber 测试报告嵌入方法
  • gitlab-ci.yml
  • ps如何批处理文件(批量裁剪,批量设置文件大小)
  • RNN(循环神经网络)
  • 青岛国瑞 RCV 带来的独特价值
  • MinIo快速入门
  • 13.计算 Python 字符串的字节大小
  • Kubernetes 高级调度 02