背景小故事
进入正题前,我们先扯一扯关于单例模式的应用场景,很多小伙伴在初期了解设计模式的时候,都会很不解为什么要这么设计,就好比工厂模式,可能很多小伙伴看完了知道了什么是工厂模式,但是在做题的时候还是不会往设计模式这方面去想。
单例模式应用场景:
需求 : 在日常生活中,班长只能有一个,我们会通过创建一个班长类,然后实例化对象,但是在这样的条件下,我们可以无限次创建班长类的对象,我们需要通过单例模式的设计,实现这个类只能被创建一次。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
我们要在班长类中将构造方法私有化,这样是防止在其他地方被实例化,就出现多个班长对象了。然后我们在班长类中自己 new 一个班长对象出来。最后给外界提供一个方法,返回这个班长对象即可。代码如下 :
饱汉式/懒汉式 :
public classMonitor {
private static Monitor monitor = null;
private Monitor() {
}
public static Monitor getMonitor() {
if (monitor == null) {
monitor = new Monitor();
}
return monitor;
}
}
了解过线程安全/同步这方面的同学应该都知道,在以上代码的条件下并不能保证一个班长类实例,因为存在线程安全问题。假如线程A执行到了monitor = new Monitor();
此时班长对象还没创建,线程B执行到判断 monitor == null时,条件为true,于是也进入到if里面去执行monitor = new Monitor();了,这样内存中就出现了两个班长实例了。
于是,将以上的代码做了修改,在获取班长对象的方法上面加了个 synchronized 关键字,这样就能解决线程安全问题了。
public static synchronized Monitor getMonitor(){
if (monitor == null) {
monitor = new Monitor();
}
return monitor;
}
但是以上代码也存在明显的问题,这种修改不太好,虽然可以解决线程安全问题,但是效率太差了,不管班长对象有没有被创建好,后面每个线程并发走到这,可想而知,都做了无用的等待呀
我们不能在方法上添加 synchronized关键字,但可以在方法内部添加。比如:
public static Monitor getMonitor() {
if (monitor == null) {
synchronized (Monitor.class) {
if (monitor == null) {
monitor = new Monitor();
}
}
}
return monitor;
}
补充说明 :
第一层判断如果 monitor 实例不为空,那皆大欢喜,说明对象已经被创建过了,直接返回该对象即可,不会走到 synchronized 部分,所以班长对象被创建了之后,不会影响到性能。
第二层判断是在 synchronized 代码块里面,为什么要再做一次判断呢?假如 monitor 对象是 null,那么第一层判断后,肯定有很多线程已经进来第一层了,那么即使在第二层某个线程执行完了之后,释放了锁,其他线程还会进入 synchronized 代码块,如果不判断,那么又会被创建一次,这就导致了多个班长对象的创建。所以第二层起到了一个防范作用。
这就是“懒汉式”单例模式,为什么称为“懒汉式”呢?顾名思义,就是一开始不创建,等到需要的时候再去创建对象。
饿汉式 :
不多BB,直接看代码
public class Monitor {
private static Monitor monitor = new Monitor ();
private Monitor(){
}
public static Monitor getMonitor(){
return monitor;
}
}
定义的时候就将班长对象创建出来,这样还没有线程安全问题。
这种方式就叫做“饿汉式”单例模式,顾名思义,一开始就创建出来,比较"饥饿","比较饥渴"不管啥了,上来就创建班长对象,这种方式是不存在线程安全问题的。这个“饿汉式”单利相对来说比较简单,也很好理解,我就不多说了。
提问:
为什么饿汉式这么好,又不存在线程安全的问题,那为什么还要有饱汉式/懒汉式呢?
其实这个问题也很好回答
不管是“懒汉式”还是“饿汉式”,都有它们各自的优缺点以及使用场景。
“饿汉式”虽然简单粗暴,而且线程安全,但是它不是延迟加载的,也就是说类创建的时候,就必须要把这个班长实例创建好,而不是在需要的时候才创建,可想而知,饿汉式执行效率相对懒汉式/饱汉式没有那么快,这是第一点。
我再举个例子,也许更能说明问题:假如在获取班长对象的时候,需要传一个参数进去呢?也就是说,我在选班长的时候有个要求,比如我想选一个身高高于175cm的人做班长,那么我在获取班长实例对象时,需要传一个身高参数,该方法就应该这样设计:
public static Monitor getMonitor(Long height){
//代码块
}
针对这种情况,“饿汉式”就不行了,就得用“懒汉式”单例了。
总结:
将构造方法用private进行修饰,提供公开(public),静态(static),同步(synchronized),getMonitor()方法