单例模式是校招中最常考的设计模式之一.
那么啥是设计模式呢?
设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.
那么什么是单例模式呢?
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
注意:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例
单例模式具体的实现方式, 又分成 “饿汉” 和 “懒汉” 两种
饿汉模式,就是它很饿,它的对象早早的就创建好了
这种方式比较常用,但容易产生垃圾对象。
类加载的同时, 创建实例
代码实现如下:
class Singleton {
//创建新实例,static修饰,只创建一次
private static Singleton instance = new Singleton();
//构造方法进行封装,不能创建新实例
private Singleton() {}
//唯一实例的获取方法
public static Singleton getInstance() {
return instance;
}
}
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
懒汉模式,顾名思义就是懒,没有对象需要调用它的时候不去实例化,有人来向它要对象的时候再实例化对象
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
因为没有加锁synchronized,所以严格意义上它并不算单例模式
代码实现如下:
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
//需要用到时进行创建
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上面的懒汉模式的实现是线程不安全的.
但是一旦实例已经创建好了, 后面再多线程环境调用 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 才能保证单例,但加锁会影响效率
该版的懒汉模式还存在一个问题,就是内存可见性的问题,不知道内存可见性的宝子可以去看看博主写的【JavaEE初阶】 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 挡住了. 也就不会继续创建其他实例.
比如一下实例:
校花答应了第一个哥们后,那哥们就走了
这时候另外一个哥们又来问:能做你男票不?
校花此时就说:我已经有男朋友了
此时这个哥们就不能成为校花的男朋友了
关于《【JavaEE】 饿汉模式与懒汉模式详解与实现》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!