单例模式的多种写法
我们要实现一个单例,首先最重要的是什么?
当然是把构造函数私有化,变成
private
类型,(为啥? 单例单例,如果谁都能通过构造函数创建对象,还叫单例吗?是不~)嗯~我们构造函数私有化后,我们应该操作啥呢?
接着我们需要提供一个方法,这个方法要保证初始化有且仅初始化一个单例对象!~
Okk~
,请再看看我们的标题,思考下,单例模式有哪几种写法呢?工作中我们最常用的是哪些呢?
好了,别想了,我们一起看答案
单例模式有五种模式: 1.懒汉,2.饿汉,3.枚举,4.静态内部类,5.双重检验锁
最常用的嘛: 当然两个单身汉啦: 饿汉和懒汉了(和我一样,都是狗~哈哈哈)
上述的说明是我们对单例模式整体有了个大概得了解,你说:这不行啊!我还是*心虚*,因为只是**纸上谈兵**,别急嘛,我们这就**此事躬行**!
万变不离其宗:
本文总纲图:
下面是五中单例模式的代码实现,(还是那句话~看不懂,我们就敲,若一遍不行,就两遍,
Practice makes perfect!
)还有在敲下面的代码之前,我们思考下(也是一道面试题)
为啥单例模式要使用
static
和final
进行声明呢???(后面我会给解答,但先思考下,会吸收事半功倍~哈哈哈)
1.懒汉模式
为啥叫懒汉模式呢?如它的名字一样,创建对象只有进程用到的时候,才进行创建
换句话说: 被动创建,比如工作,老板叫我干我就做,没叫我时,我就佛系摸鱼
具体代码:
package com.design_patterns;
/**
* <p>
* 描述: 懒汉单例$ <br>
* <p>
* 需求信息: 【需求ID与需求标题】【客户名称】 <br>
*
* @author aristo
* @date 2024/2/21 10:51
*/
public class LazyMan {
public static LazyMan instance;
//构造函数私有化
private LazyMan(){};
//获取对象
public static synchronized LazyMan getInstance(){
//发现对象为空时,创建对象;否则直接返回
if(instance==null){
instance=new LazyMan();
}
return instance;
}
}
2.饿汉模式
此模式就和懒汉就相反了,有种忧患意识,类似悲观锁的算法设计思想
啥意思?就是在程序启动时,我就立即将单例对象给创建出来—具体我们看代码就一幕了然了
package com.design_patterns;
/**
* <p>
* 描述: 饿汉模式$ <br>
* <p>
* @author zf
* @date 2024/2/21 14:31
*/
public class HungryModel {
//注意看: 我们这里是直接就创建对象了 这里面的static和final思考下为啥用(不用行不行呢?)
public static final HungryModel instance=new HungryModel();
private HungryModel (){};
public static HungryModel getInstance(){
//这里我们就直接返回
return instance;
}
}
3.枚举单例模式
写到枚举单例模式,我们在这也思考一道题目:
为什么枚举是最好的单例模式?? (哈哈哈!你别吐槽–这博主真是的,我就看个博文,咋又叫我思考噻!没错,因为我们要的是看一篇顶三篇!!!)
这我会单独写一篇文章描述,这里插个眼,标记下~哈哈哈
深入了解请阅读我的
博文
: 枚举为什么是最好的单例模式实现?
代码实现示例:
package com.design_patterns;
/**
* 描述: 单例模式---枚举方式 <br>
* @author zf
* @date 2024/2/21 16:14
*/
public enum EnumSingleton {
INSTANCE;
public void testMethod(){
}
}
4.静态内部类单例模式
所谓的静态内部类单例模式,我们可以看到在创建对象时,我们是在类中定义一个静态内部类(很关键!!!) 其实你自己敲一遍就拨开其真面目了!~
详细解说嘛,我推荐你敲一遍,就知庐山真面目了~(你说,那我敲一遍还不懂咋整啊?直接评论区@轰炸我)
package com.design_patterns;
/**
* 描述: 静态内部类单例模式 <br>
* @author zf
* @date 2024/2/21 16:18
*/
public class StaticInnerClassSingleton {
//所谓的静态内部类单例模式,我们可以看到在创建对象时,我们是在类中定义一个静态内部类(很关键!!!!) 其实你自己敲一遍就拨开其真面目了!~
private static class SingletonHolder{
private static final StaticInnerClassSingleton INSTANCE=new StaticInnerClassSingleton();
}
private StaticInnerClassSingleton(){};
public static final StaticInnerClassSingleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
5.双重检验锁单例模式
顾名思义: 就是通过两把锁检测把关后,我们创建对象,啥好处?安全性高
对于代码中的
volatile
,再思考一下,我们为啥要加??
package com.design_patterns;
/**
* <p>
* 描述: 双重检验锁单例模式 <br>
* <p>
* 需求信息: 【需求ID与需求标题】【客户名称】 <br>
*
* @author zf
* @date 2024/2/21 16:29
*/
public class DoubleLockSingleton {
//好了,别想了,我解答下: 使用volatile: 防止指令重排!!!!!(面试官问你,就抛给他这句话,完事~)
private volatile static DoubleLockSingleton doubleLockSingleton;
//私有化
private DoubleLockSingleton(){};
public static DoubleLockSingleton getInstance(){
//第一遍检验
if(doubleLockSingleton==null){
synchronized (DoubleLockSingleton.class){
//再次检验
if(doubleLockSingleton==null){
doubleLockSingleton=new DoubleLockSingleton();
}
}
}
return doubleLockSingleton;
}
}
【解答】单例模式为啥使用static
和final
关键字???
回顾我们一开始说的问题,我来解答下哈!耐心看看嘛,绝对夯实基础,出门还可**吹牛逼**!嘿嘿
1.static
的特殊含义
static
可以保证在一个线程未使用其他同步机制的情况下总是可以读到一个类的静态变量的初始值!!!(这句话建议读三遍,真的诠释了静态的真谛)
上面也许不好理解,但我们首先应该知道:
static
变量是随着类被初次访问而初始化的。比如在多线程的环境中,需要保证一个变量在线程中的可见性是需要对内存做一些操作指令的。
这么说不怎么明白吧? 举个例子:(可以不看哈,纯粹为了加深理解)
当两个线程
A,B
对一个共享变量进行操作的时候,
- 首先是把内存中的数据加载进各自的处理器中,然后在放入各自的寄存器。
- 当
A
中更新共享变量的时候,首先会被放入写缓存器中,然后再写入高速缓存中,最后是放进内存中,每个处理器都有各自的写缓存器和高速缓存。- 所以在一个时间点,线程
B
读取的共享变量值并不是A
更新的那一个,仍然很有可能是一个旧值。(类似于脏读)- 对于这个
旧值
怎么理解呢?()
- 在计算机系统设计中,为了保证变量的可见性,有一种协议叫做缓存一致性协议,
- 这个协议的作用我简单说明下:不同的处理器可以读取对方高速缓存中的值。这时我们要保证可见性只需要一步,就是当共享变量被更新的时候,原子性保证把值写入高速缓存中就可以了,
- 对于
volatile
的实现方式也是基于这种思想。- 那么static变量所做的事情就是,在某个线程调用的时候,就写入内存中这个值,保证内存中必然有这个值的存在,
- 注意:
static
并不能保证多线程之后操作的可见性。
2.final
我们都知道,final
修饰的变量值不会改变;但是在多线程的环境中,它还会保证两点,
- 其他线程所看到的
final
字段必然是初始化完毕的。final
修饰的变量不会被程序重排序。
综上所述:
static
保证了变量的初始值,final
保证了不被JIT
编译器重排序。对于一个单例模式来说,它所在的类在被引用的时候,
static
会保证它被初始化完毕,且是所有线程所见的初始化,final
保证了实例初始化过程的顺寻性。两者结合保证了这个实例创建的唯一性。
完结
好了,各位优秀的同仁们,学废了没?哈哈哈!~是不是又学到了呢?