目录
一、单例模式的概念
二、饿汉模式
三、懒汉模式
(1)懒汉模式-单线程版
(2)懒汉模式-线程安全多线程版
啥是设计模式咧🤔设计模式好比象棋中的棋谱。红方当头炮,黑方马来跳👣。针对红方的一些走法,黑方应招的时候有一些固定套路。
软件开发中也有很多常见的 "问题场景"。针对这些问题场景,,大佬们总结出了一些固定的套路.。按照这个套路来实现代码,就是设计模式。常见的设计模式有23种。
总体来说设计模式分为三大类:
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
本文主要讲述最简单的单例模式。
一、单例模式的概念
单例模式是设计模式中最简单的形式之一。单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例。
单例模式的三个要点:1.某个类只能有一个实例。2.自行创建这个实例。3.自行向整个系统提供这个实例。具体实现方式:1.单例模式的类只提供私有的构造函数。2.类定义中含有一个该类的静态私有对象。3.该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
单例模式的应用场景很多。例如:JDBC 中的 DataSource 实例就只需要一个、一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个文件管理器或者窗口管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种。
二、饿汉模式
类加载的同时, 创建实例。饿汉先把食物准备好,当饿了的啥时候就吃饭。
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
饿汉模式注意点:
1.构造方法私有化
2.private static Singleton instance = new Singleton();中static表示类方法,只能访问类成员、类变量。创建一个实例,进行初始化,整个程序,只有这么一个实例。
3.public static Singleton getInstance() 中使用static修饰的方式,也是类方法。调用的话使用Singleton.getInstance。
三、懒汉模式
类加载的时候不创建实例,第一次使用的时候才创建实例。懒汉当饿了的时候,才想着准备食物。
例如:手机淘宝,在用户打开手机淘宝时,只加载第一页的内容。当用户往下拉,再继续加载后面的内容。好处就是加载会非常快。文本文件同理,linux一些命令,比如cat命令就是使用懒加载,看一些加载一页,第一次打开时就非常快。
懒汉模式步骤:
1.现将构造方法改为私有
2.创建一个实例但不对其进行初始化
3.对外提供一个方法来获取这个实例并对其进行初始化
(1)懒汉模式-单线程版
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
(2)懒汉模式-线程安全多线程版
如果按照上述代码,那么会存在线程不安全的问题。线程安全问题发生在首次创建实例时。如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例。
我们需要做的是一旦实例已经创建好了,后面再多线程环境调用 getInstance 就不再有线程安全问题了,不再修改instance实例了。
可以通过同步synchronized 改善线程安全问题。
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在加锁的情况下进一步改进:
使用双重 if 判定, 降低锁竞争的频率.
给 instance 加上了 volatile.
class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
小贴士:
外层的 if (instance == null)是判断是否枷锁。当instance!=null的时候,两个线程同时访问,就不需要进行锁的竞争;内层的 if (instance == null)是避免重复实例化,解决线程安全问题。两个if判断互相不能代替。
加锁 /解锁是一件开销比较高的事情。对于懒汉模式的线程不安全只发生在首次创建实例的时候。因此后续使用的时候, 就不必再进行加锁了。外层的 if 就是判定当前是否已经把 instance 实例创建出来。
同时为了避免 "内存可见性" 导致读取的 instance 出现偏差,,于是补充上 volatile 。当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作。当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了。也就不会继续创建其他实例。
1) 有三个线程,开始执行 getInstance ,通过外层的 if (instance == null) 知道了实例还没有创建的消息。于是开始竞争同一把锁。
2) 其中线程1 率先获取到锁,此时线程1 通过里层的 if (instance == null) 进一步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来。
3) 当线程1 释放锁之后,线程2 和 线程3 也拿到锁,也通过里层的 if (instance == null) 来确认实例是否已经创建,发现实例已经创建出来了,就不再创建了。
4) 后续的线程不必加锁,直接就通过外层 if (instance == null) 就知道实例已经创建了,从而不再尝试获取锁了,降低了开销。
懒汉模式不可缺少的四部分:构造函数私有化、正确加锁、双重check、volatile