当前位置: 首页 > 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.lryc.cn/news/588042.html

相关文章:

  • FreeRTOS之链表操作相关接口
  • QT——文件操作类 QFile和QTextStream
  • 第一次接触自动化监测,需要付费厂家安装服务吗?比人工测量主要区别是啥?
  • 用Python向PDF添加文本:精确插入文本到PDF文档
  • 2024-2025-2 山东大学《毛概》期末(回忆版)
  • 复习笔记 38
  • linux下的消息队列数据收发
  • “国乙黑月光”指的是谁?
  • 动态规划题解——单词拆分【LeetCode】
  • 【字节跳动】数据挖掘面试题0017:推荐算法:双塔模型,怎么把内容精准地推送给用户
  • 设计模式(行为型)-迭代器模式
  • iOS App 安全加固全流程:静态 + 动态混淆对抗逆向攻击实录
  • Linux进程优先级机制深度解析:从Nice值到实时调度
  • 基于MATLAB的LSTM长短期记忆神经网络的时间序列数据预测方法应用
  • .NET ExpandoObject 技术原理解析
  • C#/.NET/.NET Core技术前沿周刊 | 第 46 期(2025年7.7-7.13)
  • 如何用深度学习实现图像风格迁移
  • 面试150 路径总和
  • 电脑升级Experience
  • WPF自定义日历选择控件
  • ZYNQ双核通信终极指南:FreeRTOS移植+OpenAMP双核通信+固化实战
  • spark广播表大小超过Spark默认的8GB限制
  • 大数据系列之:通过trino查询hive表
  • pyspark中map算子和flatmap算子
  • kettle从入门到精通 第103课 ETL之kettle kettle读取redis中的Hash数据
  • IOS开发者账号如何添加 uuid 原创
  • 图机器学习(1)——图论基础
  • [硬件电路-22]: 为什么模拟电路信号处理运算的精度不如数字信号处理运算?
  • flink 中配置hadoop 遇到问题解决
  • 基于MaxCompute MaxFrame 汽车自动驾驶数据预处理最佳实践