单例模式
- 🌴饿汉模式
- 🎍懒汉模式
- 🌸单线程版(线程不安全)
- 🌸多线程版
- 🌸多线程版(改进)
- ⭕总结
单例模式是校招中最常考的 设计模式之⼀.
啥是设计模式?
- 设计模式好⽐象棋中的 “棋谱”. 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀ 些固定的套路.按照套路来⾛局势就不会吃亏.
- 软件开发中也有很多常⻅的 “问题场景”. 针对这些问题场景, ⼤佬们总结出了⼀些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例.
啥是单例模式?
- 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
- 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
注意:
-
单例类只能有一个实例。
-
单例类必须自己创建自己的唯一实例。
-
单例类必须给所有其他对象提供这一实例
单例模式具体的实现⽅式有很多. 最常⻅的是 “饿汉” 和 “懒汉” 两种.
🌴饿汉模式
类加载的同时, 创建实例.(类加载的时候就会创建类实例)
代码示例
class Singleton {
// instance被static 修饰
private static Singleton instance = new Singleton();
// 构造方法被private修饰,不能通过构造方法获取类实例
private Singleton() {}
//只能通过getinstance方法获取唯一实例
public static Singleton getInstance() {
return instance;
}
}
注意instance是被static修饰的。
static表示静态的,这里修饰类,指的是“类属性”,instance即使类对象里面持有的属性。
SIngleton.class(从class文件加载到内存中表示这个类的一个数据结构,每个类只存在一个类对象)
因此instance指向的这个对象,就是唯一的一个对象!!
构造方法被private修饰,不能通过构造方法获取类实例
只能通过getinstance方法获取唯一实例
-
优点:没有加锁,执行效率会提高。
-
缺点:类加载时就初始化,浪费内存。
🎍懒汉模式
类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.
懒汉模式,顾名思义就是懒,没有对象需要调用它的时候不去实例化,有人来向它要对象的时候再实例化对象
🌸单线程版(线程不安全)
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
因为没有加锁synchronized,所以严格意义上它并不算单例模式
上面的懒汉模式的实现是线程不安全的.
- 线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 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;
}
}
-
优点:第一次调用才初始化,避免内存浪费。
-
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率
该版的懒汉模式还存在一个问题,就是内存可见性的问题,不知道内存可见性的宝子可以去看看博主写的volatile 关键字、wait 和 notify方法详解里面对volati关键字部分的讲解
🌸多线程版(改进)
所以我们针对上述多线程版的懒汉模式进行了改进
以下代码在加锁的基础上, 做出了进一步改动:
-
使用双重 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 判定 / volatile是怎么达到这样的效果的呢?
-
加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.
-
因此后续使用的时候, 不必再进行加锁了.
-
外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
-
同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是补充上 volatile .
-
当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作.
-
当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
比如以下实例
-
有三个线程, 开始执⾏ getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同⼀把锁
-
其中线程1 率先获取到锁, 此时线程1 通过⾥层的 if (instance == null) 进⼀步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.
-
当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过⾥层的 if (instance == null) 来
确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了
-
后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了,从⽽不再尝试获取锁了. 降低了开销.、
⭕总结
关于《【多线程】多线程案例——单例模式详解(包含懒汉模式,饿汉模式)》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!