【秒懂设计模式】单例设计模式

简介:  秒懂设计模式——单例设计模式 (三)单例设计模式 1.先解释一下,什么是单例模式呢? 在Java中是这样定义的:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。” 显然从单例模式的定义中,我们可以发现它有三个要点: ①某个类只能有一个实例; ②它必须自行创建这个实例; ③它必须自行向整个系统提供这个实例。 2.要满足这三个要点,应该如何实现

 秒懂设计模式——单例设计模式


(三)单例设计模式

1.先解释一下,什么是单例模式呢?

Java中是这样定义的:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。

显然从单例模式的定义中,我们可以发现它有三个要点

①某个类只能有一个实例;

②它必须自行创建这个实例;

③它必须自行向整个系统提供这个实例。

2.要满足这三个要点,应该如何实现呢?下面让我们来逐条分析:

①如何保证某个类只能有一个实例?

让我先来想一下,一个类的对象是如何创建的呢?答案是:一个类的对象的产生是由类构造函数来完成。那么,我们是不是可以通过私有类构造函数来实现呢?

②如何保证类定义中含有一个该类的静态私有对象?

这个就很简单了,让单例类自己创建自己的唯一实例

③如何保证它自行向整个系统提供这个实例?

这个也不难,对外提供获取此对象的静态方法即可。

3.单例模式分为几种呢?

①懒汉式,线程不安全;

②懒汉式,线程安全;

③饿汉式;

④双检锁/双重校验锁(DCL,即 double-checked locking);

⑤登记式/静态内部类;

⑥枚举式;

下面仍然通过一个故事,逐一介绍这几种类型的单例模式,并且在最后,还会分析一下他们的性能差异。

【讲故事】某野鸡大学(),只有一个学生会主席(唯一对象,自行实例化),还是一个漂亮妹子叫“M蓉”,其他附近野鸭大学的学长都喜欢请她去自己宿舍,连夜补习英语(向整个系统提供实例)。

1)懒汉式,线程不安全

先解释一下为什么叫懒汉式:“懒”顾名思义就是延迟,懒汉式就是指,只有在获取此单例对象时,才会去创建此对象

Java代码】

①创建一个单例模式的类。

package com.liyan.lazy;
/**
 * 懒汉式的M蓉(线程不安全)
 * <p>Title: LazyMRong</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午2:00:44
 */
public class LazyMRong {
	//1.私有空参构造,防止别人创建
	private LazyMRong(){}
	//2.自己创建自己的唯一实例
	private static LazyMRong lazyMRong ;
	//3.对外提供获取此对象的静态方法
	public static LazyMRong getLazyMRong() {
		if (lazyMRong == null) {
			//懒汉式意味着,只有你在想获取时,才创建此对象
			return new LazyMRong();
		}
		return lazyMRong;
	}
	public void teachEnglish() {
		System.out.println("连夜补习外语!");
	}
}

②创建外部访问

package com.liyan.lazy;
/**
 * S喆请M蓉补习外语
 * <p>Title: SZhe</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午2:15:23
 */
public class SZhe {
	public static void main(String[] args) {
		//通过静态方法,获取到单例对象
		LazyMRong lazyMRong = LazyMRong.getLazyMRong();
		//调用补习外语方法
		lazyMRong.teachEnglish();
	}
}

分析:首先懒汉式肯定是延迟初始化的,但是不支持多线程。因为没有加锁synchronized,所以有些资料上认为,从严格意义上讲,它并不算单例模式

2)懒汉式,线程安全

这个跟上面的懒汉式,线程不安全,唯一的区别就是:在获取单例类的静态方法上,加上了synchronized关键字进行修饰

package com.liyan.lazy;
/**
 * 懒汉式的M蓉(线程安全)
 * <p>Title: LazyMRong</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午2:00:44
 */
public class LazyMRong {
	//1.私有空参构造,防止别人创建
	private LazyMRong(){}
	//2.声明自己的唯一实例,先不实例化
	private static LazyMRong lazyMRong ;
	//3.对外提供获取此对象的静态方法
	public synchronized static LazyMRong getLazyMRong() {
		if (lazyMRong == null) {
			//懒汉式意味着,只有你在想获取时,才创建此对象
			return new LazyMRong();
		}
		return lazyMRong;
	}
	public void teachEnglish() {
		System.out.println("连夜补习外语!");
	}
}

3)饿汉式分析:毋庸置疑,它是延迟初始化的,能够在多线程中很好的工作,同时在第一次调用时,才进行初始化,避免了内存的浪费。但是加了synchronized 锁机制,所以效率很低

先解释一下为什么叫饿汉式:“饿”意味着“着急”,饿汉式就是指,不管单例对象有没有外部访问,先实例化再说

Java代码】饿汉式的M蓉

package com.liyan.hungry;
/**
 * 饿汉式的M蓉(线程安全)
 * <p>Title: HungryMRong</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午3:02:18
 */
public class HungryMRong {
	//1.私有空参构造,防止别人创建
	private HungryMRong(){}
	//2.创建自己的唯一对象,并直接实例化(饿汉式,因为饿着急,不管三七二十一,直接实例化)
	private static HungryMRong hungryMRong = new HungryMRong();
	//3.对外提供获取此对象的静态方法
	public static HungryMRong getHungryMRong() {
		return hungryMRong;
	}
}

4)双重检查锁(DCL,即 double-checked locking)分析:显然饿汉式,不懒,所以没有延迟初始化,同时它基于 classloder 机制,而没用加锁的方式,所以既避免了多线程的同步问题,又使得执行效率得到提高。但是,因为它在类加载时就初始化,所以会造成内存的浪费

先解释一下为什么叫双重检查锁:顾名思义,会涉及到两把锁:

①进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块;

②进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建该实例。

Java代码】双重检查锁的M蓉

package com.liyan.dcl;
/**
 * DCL双重检查锁的单例模式的M蓉
 * <p>Title: DCLMrong</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午4:54:30
 */
public class DCLMrong {
	//1.私有空参构造,防止别人创建
	private DCLMrong (){}
	//2.声明唯一的单例对象;注意这里用到了volatile关键字!
	private volatile static DCLMrong dclMrong ;
	//3.对外提供获取此对象的静态方法
	public static CLMrong getDclMrong() {
		//第一把锁:先检查实例是否存在,如果不存在才进入下面的同步块;
		if (dclMrong == null) {
			synchronized (DCLMrong.class) {
				//第二把锁:再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。
				if (dclMrong == null) {
					return new DCLMrong();
				}
			}
		}
		return dclMrong;
	}
}

分析:这种方式采用双锁机制,安全且在多线程情况下能保持高性能说明: 双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量

5)登记式/静态内部类

先解释一下为什么叫登记式:见名知意,想要获取某单例对象,没登记的必须先登记,然后才能获取。此时我们还需要一个登记簿(Map集合),当然,这个登记簿可以记录一堆单例对象,而不仅仅是一个。

这个模式确实复杂了一些,而且很多网上的例子也是觉得有所不妥,我根据个人理解,写了如下的代码,如果各位看官觉得也有疑问,欢迎留言讨论。

Java代码】

①创建登记式单例模式类

package com.liyan.register;
import java.util.HashMap;
import java.util.Map;
/**
 * 登记式单例模式
 * <p>Title: RegisterMrong</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午6:38:22
 */
public class RegSingleton {
	//1.私有空参构造,防止别人创建
	private RegSingleton(){}
	//2.创建一个登记簿,在静态代码块中创建自己的唯一对象
	private static Map<String, RegSingleton> map = new HashMap<String, RegSingleton>(0);  
	//在登记簿上登记
	private static void singletonHolder(String name) {
		if(name==null){  
            name="RegSingleton"; 
        } 
		RegSingleton singleton = new RegSingleton();  
		//利用反射获取类名,并作为map集合的key
		map.put(name, singleton);
		System.out.println("已将"+ name +"记录到登记簿!");
	}
	//3.对外提供获取此对象的静态方法
	public static RegSingleton getInstance(String name){  
        if(name==null){  
            name="RegSingleton"; 
            System.out.println("名字为空,自动找RegSingleton!");
        }  
        //查询登记簿上是否登记过
        if(map.get(name)==null){  
        	System.out.println("名字"+name+"未在登记簿中登记!");
            try {  
            	//如果没有登记过,则先进行登记
            	System.out.println("名字"+name+"开始在登记簿中登记!");
            	RegSingleton.singletonHolder(name);
            	//登记之后再返回该对象
            	System.out.println("名字"+name+"已经登记完成!");
            	return map.get(name);
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }else {
        	//如果登记过,直接返回该单实例
        	System.out.println("名字"+name+"之前在登记簿中登记过了!");
        	return map.get(name);  
		}
		return null;
    } 
	public void teachEnglish() {
		System.out.println("连夜补习外语!");
	}
}

②创建外部访问

package com.liyan.register;
/**
 * S喆请M蓉补习外语
 * <p>Title: SZhe</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午2:15:23
 */
public class SZhe {
	public static void main(String[] args) {
		RegSingleton mrong = RegSingleton.getInstance("Mrong");
		mrong.teachEnglish();
	}
}

③结果

名字Mrong未在登记簿中登记!
名字Mrong开始在登记簿中登记!
已将Mrong记录到登记簿!
名字Mrong已经登记完成!
连夜补习外语!

①相同点:都利用ClassLoder机制,来保证初始化时只有一个线程。分析:主要是比较一下,登记模式和双重检验锁式有何异同?

②不同点:双重检验锁式在类一被装载是就被初始化了,所以它没有延迟的效果;而登记模式,只有再主动调用获取该对象的静态方法时,才被初始化,所以它有延迟效果。

6)枚举式

先解释一下为什么叫枚举式:不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,但是《Effective Java》一书中的话有这样一段很经典的话:“单元素的枚举类型已经成为实现Singleton的最佳方法!

Java代码】枚举单例模式的M蓉

package com.liyan.enummodel;
/**
 * 枚举单例模式的M蓉
 * <p>Title: EnumMrong</p>  
 * @author  Liyan  
 * @date    2017年4月27日 下午9:23:59
 */
public class EnumMrong {
	//1.私有空参构造,防止别人创建
	private EnumMrong() {}
	//2.申明自己的唯一对象
	public static EnumMrong getInstance() {
		return Singleton.INSTANCE.getInstance();
	}
	//3.对外提供获取此对象的静态方法
	private static enum Singleton {
		INSTANCE;
		private EnumMrong singleton;
		//在构造方法中实例化对象,保证只调用一次
		private Singleton() {
			singleton = new EnumMrong();
		}
		public EnumMrong getInstance() {
			return singleton;
		}
	}
}



 
 

相关文章
|
7月前
|
设计模式
单例设计模式步骤
单例设计模式步骤
37 1
|
7月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
153 0
|
7月前
|
设计模式 安全 Java
【设计模式】2、设计模式分类和单例设计模式
【设计模式】2、设计模式分类和单例设计模式
61 0
|
7月前
|
设计模式 消息中间件 安全
多线程编程设计模式(单例,阻塞队列,定时器,线程池)(二)
多线程编程设计模式(单例,阻塞队列,定时器,线程池)(二)
64 1
|
7月前
|
设计模式 Java
26、Java 简单实现单例设计模式(饿汉式和懒汉式)
26、Java 简单实现单例设计模式(饿汉式和懒汉式)
59 2
|
7月前
|
设计模式 安全 Java
在Java中即指单例设计模式
在Java中即指单例设计模式
42 0
|
1月前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
33 2
|
3月前
|
设计模式 存储 安全
设计模式——设计模式介绍和单例设计模式
饿汉式(静态常量)、饿汉式(静态代码块)、懒汉式(线程不安全)、懒汉式(线程安全,同步方法)、懒汉式(线程不安全,同步代码块)、双重检查(推荐,线程安全、懒加载)、静态内部类(推荐)、枚举(推荐)
|
7月前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
4月前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
66 1