简单学习 --> 单例模式

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

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

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

例如: 很多管理数据的对象就应该要是"单例"的, 例如 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. 使用序列化 , 反序列化,打破单例

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

相关推荐
Henray20241 小时前
LRU缓存设计与实现
java·面试
这个名字先用着1 小时前
形位公差速查详解
学习·汽车·制造
甲方大人请饶命2 小时前
SSM-基础
java·数据库·spring
Jackyzhe2 小时前
从零学习Kafka:幂等与事务
数据库·学习·kafka
谷雨不太卷2 小时前
Linux基础IO
java·开发语言
小新同学^O^2 小时前
简单学习 --> 文件IO
java·学习·文件io
学习使我快乐012 小时前
Express 学习
学习·node.js·express
熠熠仔2 小时前
《Agentic Design Patterns》概览
学习·设计模式
吴声子夜歌2 小时前
Java——Arrays
java·算法·排序算法