> 文档中心 > Java设计模式之单例模式【创建型】

Java设计模式之单例模式【创建型】


目录

​编辑

前言

开篇

什么是单例模式

单例模式能解决什么问题?

项目中哪些地方使用了单例模式?

如何实现单例模式?

第一版

第二版【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;    }}

我们都知道静态内部类是在调用的时候才会去初始化,所以我们可以利用这个特性实现单例的创建,既能做到线程安全也能做到懒加载,更是简化了代码;

以上能做到单例模式,但是都会被反射破坏,反射可以忽略任何,它可以直接调用构造器,即使你的构造器是私有的,但是这点可以不需要考虑,反射基本是人为的,只要你不去恶意的搞事情,基本忽略,如果硬要做到的话可以自己去了解下枚举类,使用枚举类实现单例的话可以做到不能被反射破坏,也能做到线程安全,但是它不是懒加载的,感兴趣的话自己私下了解下吧!

今天就到这里啦~对你有帮助的话不妨留个赞呗!

 🥇原创不易,还希望各位大佬支持一下!

👍点赞,你的认可是我创作的动力 !

🌟收藏,你的青睐是我努力的方向!

✏️评论,你的意见是我进步的财富! 

四四频道