目录
?编辑
前言
开篇
什么是单例模式?
单例模式能解决什么问题?
项目中哪些地方使用了单例模式?
如何实现单例模式?
第一版
第二版【1.0】
第二版【2.0】
第二版【3.0】
第二版【最终版】
第三版
前言
大家好,我是爷爷的茶七里香,最近突然想做一个设计模式的系列,大家都知道设计模式有23种,而设计模式是由前人大量实践总结出来的,它是解决一些问题的最佳解决方案,我们今天就讲下单例模式中相对简单的单例模式吧。
开篇
什么是单例模式?
单例模式是在程序运行过程中一个类只有一个实例对象,不管调用了多少次,调用的都会是这个对象。
单例模式能解决什么问题?
首先我们使用单例模式可以提高我们的系统性能,试想一下,某个可以复用的对象创建过程十分复杂,每使用一次就要进行创建销毁的过程,?那么就浪费了不必要的资源了,一个简单的例子就是用来和数据库链接的对象,创建这个对象是比较消耗资源的,像这种对象每进行一次链接就创建一次新的对象,即浪费了空间又浪费了时间。
项目中哪些地方使用了单例模式?
在项目中我们的controller层调用service层、service层调用dao层,这两种情况使用的就是单例模式,它们为什么使用的是单例模式呢?因为它们的成员变量是不会改变的,它是静态的,如果说它们中的有一个成员变量的值是动态改变的,那么这个时候就不能用单例模式了,如果这个时候使用单例模式那么就会出现变量被污染的情况,比如两个线程去做操作,因为两个线程操作的是同一个对象,假设这个对象的成员变量有一个变量a,线程1去把a变量值修改为1,然后线程2去把a变量值修改为2,那么这个线程1读到的a变量就不是1而是2了,只有使用多利模式,让它们操作的对象不是同一个就不会出现这种情况了。想一下,controller层调用service会出现变量动态改变的情况吗?显然不会吧!因而才用单例模式!
如何实现单例模式?
要实现单例模式要看以下几点:
- 是否懒加载
- 是否线程安全
- 是否能被反射破坏(这种情况基本上是人为的,不需要考虑)
第一版
public class OneObject {
// 私有构造器
private OneObject() {}
private static OneObject oneObject = new OneObject();
public static OneObject getOneObject() {
return oneObject;
}
}
测试:
public static void main(String[] args) {
System.out.println("OneObject1 = " + OneObject.getOneObject());
System.out.println("OneObject2 = " + OneObject.getOneObject());
System.out.println("OneObject3 = " + OneObject.getOneObject());
}
打印结果:
OneObject1 = xyz.keydoisdls.test.OneObject@312b1dae OneObject2 = xyz.keydoisdls.test.OneObject@312b1dae OneObject3 = xyz.keydoisdls.test.OneObject@312b1dae?
这种写法可以避免了性能的浪费也能做到线程安全,但是它并不是懒加载的,因为它是在项目启动就把对象创建好了,如果这个对象一直都用不到,那么就浪费内存了,这种写法用不用就看个人情况吧!?
注:懒加载的意思是当我需要使用这个对象的时候才去创建,并不会在项目启动的时候就把对象创建好了!?项目启动就把对象创建好了,可能会一辈子都用不到这个对象,那么就浪费资源了,所以使用懒加载可以减少资源的占用。
第二版【1.0】
public class OneObject {
// 私有构造器
private OneObject() {}
// 懒加载
private static OneObject oneObject = null;
public static OneObject getOneObject() {
if (oneObject == null){
// 第一次进来是空的,创建一个实例
oneObject = new OneObject();
}
// 不为空直接返回
return oneObject;
}
}
这种写法可以做到懒加载,但是如果是多线程的情况下会发生什么呢?可能会出现两条线程同时进入进行null值判断,这样就出现了一个类有两个实例了,这样它就不是单例了,所以我们需要考虑是否安全的情况,因此这种写法不采用,会出现线程不安全的情况。?
第二版【2.0】
public class OneObject {
// 私有构造器
private OneObject() {}
private static OneObject oneObject = null;
// 相比第一种多了synchronized关键字(自动锁)
public static synchronized OneObject getOneObject() {
if (oneObject == null){
// 第一次进来是空的,创建一个实例
oneObject = new OneObject();
}
// 不为空直接返回
return oneObject;
}
}
可以看到在函数上多了一个synchronized关键字,这种写法可以做到懒加载+线程安全,但是比较消耗性能,你想一下,我们什么时候需要加锁呢?只有在创建对象的时候加,对吧?但是现在我连获取一个对象都要线程去竞争,那么就很浪费性能了,所以这种写法也不可取!?
第二版【3.0】
public class OneObject {
// 私有构造器
private OneObject() {}
// volatile关键字避免指令重排导致空指针
private volatile static OneObject oneObject = null;
public static OneObject getOneObject() {
if (oneObject == null){
// 对第一次进来创建对象加锁
synchronized (OneObject.class){
oneObject = new OneObject();
}
}
// 不为空直接返回
return oneObject;
}
}
?该写法相较于上一个版本避免了性能的问题,只有在创建对象的时候上锁,获取对象不会进行上锁,但是这个写法还是会有问题,如果两条线程同时进行判空操作,两条线程不管是哪条先抢到锁执行了new OneObject()的操作,另外一条也肯定会执行该操作,所以也会出现线程不安全的情况。
第二版【最终版】
public class OneObject {
// 私有构造器
private OneObject() {}
// volatile关键字避免指令重排导致空指针
private volatile static OneObject oneObject = null;
public static OneObject getOneObject() {
if (oneObject == null){
// 对第一次进来创建对象加锁
synchronized (OneObject.class){
// 再次判断
if (oneObject == null){
oneObject = new OneObject();
}
}
}
// 不为空直接返回
return oneObject;
}
}
这种写法可以完美的解决了懒加载+线程安全,看代码可以看到我们进行了两次判断,这个也叫做双检锁,让我们假设两条线程同时通过了第一个判断,第一条线程恰好抢到锁了,执行代码块的内容,又进行了一次判断,这个时候肯定是空的,创建好了对象并赋值释放锁;轮到第二条线程进入代码块了,当它又进行一次判断时发现不为空了,它就不做后续的操作了,然后释放锁,返回对象;
第三版
public class OneObject {
// 私有构造器
private OneObject() {
}
// 静态内部类
private static class One {
private static final OneObject ONE_OBJECT = new OneObject();
}
public static OneObject getOneObject(){
return One.ONE_OBJECT;
}
}
我们都知道静态内部类是在调用的时候才会去初始化,所以我们可以利用这个特性实现单例的创建,既能做到线程安全也能做到懒加载,更是简化了代码;
以上能做到单例模式,但是都会被反射破坏,反射可以忽略任何,它可以直接调用构造器,即使你的构造器是私有的,但是这点可以不需要考虑,反射基本是人为的,只要你不去恶意的搞事情,基本忽略,如果硬要做到的话可以自己去了解下枚举类,使用枚举类实现单例的话可以做到不能被反射破坏,也能做到线程安全,但是它不是懒加载的,感兴趣的话自己私下了解下吧!
今天就到这里啦~对你有帮助的话不妨留个赞呗!
?🥇原创不易,还希望各位大佬支持一下!
👍点赞,你的认可是我创作的动力?!
🌟收藏,你的青睐是我努力的方向!
??评论,你的意见是我进步的财富!?
|