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

简介:  秒懂设计模式——单例设计模式 (三)单例设计模式 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;
		}
	}
}



 
 

相关文章
|
1月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
61 0
|
1月前
|
设计模式 缓存 安全
单例设计模式的优缺点
单例设计模式的优缺点
32 0
|
1月前
|
设计模式 XML 存储
关于 ABAP 单例设计模式的一个冷门知识点
关于 ABAP 单例设计模式的一个冷门知识点
22 0
|
1月前
|
设计模式 安全 Java
【设计模式】2、设计模式分类和单例设计模式
【设计模式】2、设计模式分类和单例设计模式
25 0
|
1月前
|
设计模式 Java
26、Java 简单实现单例设计模式(饿汉式和懒汉式)
26、Java 简单实现单例设计模式(饿汉式和懒汉式)
28 2
|
3月前
|
设计模式 消息中间件 安全
多线程编程设计模式(单例,阻塞队列,定时器,线程池)(二)
多线程编程设计模式(单例,阻塞队列,定时器,线程池)(二)
34 1
|
3月前
|
设计模式 安全 Java
最简单的设计模式是单例?
单例模式可以说是Java中最简单的设计模式,但同时也是技术面试中频率极高的面试题。因为它不仅涉及到设计模式,还包括了关于线程安全、内存模型、类加载等机制。所以说它是最简单的吗?
56 3
最简单的设计模式是单例?
|
28天前
|
设计模式 安全 Java
在Java中即指单例设计模式
在Java中即指单例设计模式
18 0
|
5月前
|
设计模式 存储
static应用之 单例设计模式(饿汉单例&懒汉单例)
本章我们来学习单例模式中的饿汉单例和懒汉单例,那么什么是单例模式呢?应用该模式的这个类永远只有一个实列,即一个类只能创建一个对象例如电脑上的任务管理器对象只需要一个就能解决问题,可以节省内存空间先定义一个类,把构造器私有如下图,先来看一下没有把构造器私有化的SingleInstance类,此时Test类中可以随意创建多个SingleInstance的实例化。 在SingleInstance类中用private修饰无参构造器,此时左边new方法报错了。我们在右边创建一个静态变量来存储对象,变量名为instan
24 0
|
6月前
|
设计模式 安全 Java
【设计模式】单例设计模式
1、前言 单例模式是一种设计模式,它确保一个类只能创建一个实例,并提供一种全局访问这个实例的方式。在Java中,单例模式可以通过多种方式来实现,其中最常见的是使用私有构造函数和静态方法实现
33 0