懒汉模式和饿汉模式

目录

单例模式

饿汉模式

懒汉模式


单例模式

所谓单例模式,就是在有些场景中,有些特定的类,只能创建一个实例(对象),当程序员不小心创建多个实例,就会出现编译报错.

★ 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

★单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

单例模式又分为饿汉模式和懒汉模式

饿汉模式

所谓饿汉模式,就是在类加载阶段,就把实例创建出来,这种效果就给人一种特别急切的感觉,就称为单例模式.

实现方式

懒汉模式和饿汉模式_第1张图片

这个代码通过两个关键操作 保证实例是唯一的

        ★static这个操作,是让当前的instance 属性是类属性,类属性是长在类对象上的,类对象有事唯一实例,所以保证了这个实例是唯一的

        ★将构造方法设为private,外面的代码是无法通过new在来创建这个实例的

在多线程操作中,饿汉模式只有一个retutn操作,这只是一个"读操作",是线程安全的

懒汉模式

这个实例并非在类加载的时候创建的,而是在第一次真正使用的时候,才去创建,避免内存的浪费.

懒汉模式和饿汉模式_第2张图片

在多线程操作中,懒汉模式既有读操作也有写操作 ,下面画图演示

懒汉模式和饿汉模式_第3张图片

在上面的操作中,多个线程读到的instance的值都是null,就会多次触发new 操作,此时就是线程不安全的.上面的线程安全问题,本质上是,读和写的操作不是原子的,导致t2线程读到的值是t1还没来得及修改的,可以通过加锁解决线程安全问题.

懒汉模式和饿汉模式_第4张图片

上面代码导致每次getInstance的时候都需要加锁,但加锁是有开销的,所以只需要在new 对象之前加锁是有必要的, 一旦new 对象之后,调用getInstance的值都是非空的,就会直接触发return 操作,相当于一个比较操作,一个返回操作,都是读操作,此时不加锁也没事,对上面代码在进行改进,当instance为空的时候在进行加锁

懒汉模式和饿汉模式_第5张图片

在上面的代码中还会出现内存可见性问题和指令重排序问题,在这里就要先理解一下Instance = new SingletonLazy这个操作了,这个操作分为三步

1. 申请内存空间

2. 调用构造方法,把这个内存空间初始化成一个合理的值

3. 把内存空间的值赋给Instance引用. 

此时编译器为了提高运行效率,可能会进行指令重排序,调整代码执行顺序,导致多线程操作出现问题.

理解双重 if 判定  和 volatile操作:
          ★ 加锁 / 解锁是一件开销比较高的事情 . 而懒汉模式的线程不安全只是发生在首次创建实例的时候 . 因此后续使用的时候, 不必再进行加锁了 .
          ★ 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了 .
          同时为了避免 "内存可见性 " 导致读取的 instance 出现偏差 , 于是补充上 volatile .
          当多线程首次调用 getInstance, 大家可能都发现 instance null, 于是又继续往下执行来竞争锁 , 其中竞争成功的线程, 再完成创建实例的操作 . 当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了 . 也就不会继续创建其他实例 .
在对上面代码进行完善
懒汉模式和饿汉模式_第6张图片

至此一个完整版的懒汉模式就完成了 

你可能感兴趣的:(单例模式)