简单学习 --> 单例模式

单例模式 (非常经典的设计模式)

单例 => 单个实例(对象)

在某些场景中,希望有些类,是只能有一个实例的

例如: 很多管理数据的对象就应该要是"单例"的, 例如 DataSource ;

如果不使用单例模式, 就全靠程序员自己自觉了,例如: 一个类只能有一个对象,但是程序员一段时间后忘记了,有创建了新对象,或者换了一个人来写代码,这个人不知道只能有一个对象,就疯狂new新对象 ;

单例模式让编译器强制要求,保证这个类的对象不会 出现多个

同样的思想例如: final , interface , @Override , throws ;

语法中没有单例这样的支持, 所以通过我们的编程技巧来达成;

实现单例模式

饿汉模式(在类被加载的时候就创建了类的实例,比较早)

  1. 创建一个Singleton类 (single : 唯一的,仅有一个的)

  2. 让类中有一个静态的实例 , 静态成员, 在类加载的时候创建;

  3. 创建一个静态的方法获取类的实例, 静态方法通过类名调用,就不用对象调用

  4. 让 类的构造方法 private ,让其他地方无法new 类的对象

复制代码
class Singleton{
    // 创建一个静态的 实例对象
    private static Singleton instance = new Singleton();
    // 创建一个静态方法,用来获取类的实例
    public static Singleton getInstance(){
        return instance;
    }
    //将构造方法给private,让其他地方无法new对象
    private Singleton(){};
}

这时候只能通过getInstance(),来获取类的实例,并且不能new 一个对象

懒汉模式 (相比第一个没有那么早,在第一次使用的时候才创建实例)

和上面饿汉的区别,

在首次调用 getInstance() 的时候,才会真正创建出实例 , (如果一直都没有调用,就不创建实例)

复制代码
class Singleton2{
    //让类的实例为null
   private static Singleton2 instance = null ;
   //一个静态方法里,如果第一次调用才创建类的实例
   public static Singleton2 getInstance(){
       if(instance == null){
           instance = new Singleton2();
       }
       return instance;
   }
   private Singleton2(){};
​
}

饿汉式和懒汉式的例子;

一个文件10个g,

  1. 把全部内容都加载了,到内存中, 在把内容显示出来, (加载过程漫长)

  2. 把小部分数据加载到内存中,显示这部分内容,等用户翻页了,在动态加载其他内容

上面的饿汉和懒汉模式的 线程安全问题

上面的饿汉模式是线程安全的, 懒汉模式是线程不安全的 (懒汉的if 和 new 不是原子的)

1. 用加锁, 解决懒汉模式的线程安全问题后, 但是懒汉模式又变的不高效了(效率低了)

加锁后,每次线程都要判断锁,如果锁竞争还要阻塞掉, 这就非常的降低效率,本来懒汉模式,就是为了比饿汉模式高效点

2. 多加一个if判断 ,在用来判断是否要加锁;

懒汉模式只有在 第一次 调用的时候才涉及 线程不安全,加了锁之后,虽然安全了,但是效率不高了;

多一个if 判断是否要加锁, 这样就可以只在第一次调用时,才出现锁,后面就都不会加锁

虽然两个if看起来一样,但是两个if都不一样, 第一个用来判断是否要加锁, 第二个用来判断是否要new对象,

只有在第一次调用才会出现锁竞争, 后续都没有锁竞争,提高效率;

指令重排序问题

指令重排序和 内存可见性 同样是 编译器给我们优化时, 可能出现的线程安全问题;

指令重排序 : 在不改变代码原来的逻辑情况下, 编译器给我们的代码进行优化, 调整我们的代码顺序, 来提高效率;

虽然我们买的是同样的东西,但是经过编译器的优化,指令重排序 ; 效率提高了

但是在多线程中就可能会出现线程安全问题 ;

new 一个对象 可能会出现线程安全问题

我们上面已经设置了两个if() 和 加锁 来防止 new 出多个对象 ;但是 new 一个对象 可能会出现问题

new 一个对象要做的步骤

  1. 首先 要申请内存空间

  2. 然后在内存空间里 , 调用构造方法,初始化对象

  3. 返回内存指针 给 instance

一旦new 一个对象 的顺序 是 1. 3 . 2 . , 然后 开始调度t2线程, t2 线程在第一个if就发现 instance不为空, 拿到了Instance (未初始化的非法对象)就出现大问题了

此时指令重排序, t2 线程 拿到一个非法的对象 , 掉用 对象里的成员 , 就会出现问题

加volatile 解决 指令重排序问题

给 instance 加上 volatile , 就编译器就不会给我们进行优化了, 就不会 让new 的顺序改变 ;

单例总结

懒汉模式分3步骤

  1. if判断加锁 , 让 if 判断和 new 变的原子

  2. 再加一个if判断 , 是否要加锁

  3. 让new 对象 , 不出现指令重排序 , 加上volatile (同时解决"内存可见性")

这3点保证了在一般日常的情况想单例的稳定 ;

仍要其他问题打破单例模式
  1. 使用反射 , 打破单例模式

  2. 使用序列化 , 反序列化,打破单例

但都是过于挑刺了, 一般日常 不会使用到这两个来打破 单例, 这两个情况的使用范围都非常严谨的

相关推荐
无风听海4 分钟前
JSON Web Token(JWT)完全指南
java·前端·json
JAVA社区44 分钟前
Java高级全套教程(十一)—— Kubernetes 超详细企业级实战详解
java·运维·微服务·容器·面试·kubernetes
tedcloud1231 小时前
cc-switch评测:多AI Coding Agent管理工具详解
数据库·人工智能·sql·学习·自动化
胡图图不糊涂^_^2 小时前
测试BUG篇
学习·bug·测试
在繁华处2 小时前
Java从零到熟练(九):并发编程基础
java·开发语言
木头程序员2 小时前
SSM框架学习笔记
java·开发语言·mysql·spring·maven
李白你好2 小时前
页面资产梳理 · 技术指纹识别 · Spring 端点探测
java·后端·spring
一起逃去看海吧2 小时前
dify-03
java·linux·开发语言
我是一颗柠檬2 小时前
【Java后端技术亮点】热Key探测与本地缓存二级防护:Redis热点问题的终极解决方案
java·redis·后端·缓存·中间件
Refrain_zc3 小时前
Android 音视频通话核心 —— 音频编码(AAC)完整解析
java