单例模式-分解步骤,逐步解析

背景小故事

进入正题前,我们先扯一扯关于单例模式的应用场景,很多小伙伴在初期了解设计模式的时候,都会很不解为什么要这么设计,就好比工厂模式,可能很多小伙伴看完了知道了什么是工厂模式,但是在做题的时候还是不会往设计模式这方面去想。
单例模式应用场景:
在这里插入图片描述
需求 : 在日常生活中,班长只能有一个,我们会通过创建一个班长类,然后实例化对象,但是在这样的条件下,我们可以无限次创建班长类的对象,我们需要通过单例模式的设计,实现这个类只能被创建一次。

单例模式有以下特点:
  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()方法
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值