设计模式之单件模式(Singleton Pattern)

一.单件模式是什么?

单件模式也被称为单例模式,它的作用说白了就是为了确保“该类的实例只有一个”

单件模式经常被用来管理资源敏感的对象,比如:数据库连接对象、注册表对象、线程池对象等等,这种对象如果同时存在多个的话就会造成各种不一致的麻烦(你总不希望发生数据库重复连接的异常吧)

二.如何保证类的实例只有一个?

(这个问题看似简单,但如果没有接触过单件模式的话,要自己想出来解决方案还是需要一些天赋的。。不信的话,可以试着想想。。)

1.类的实例可能只有一个吗?貌似只要知道类名就可以随便new了吧?

当然可以,知道类名的话,确实可以调用其构造方法来new实例,但是,注意一点:这个类必须要有公开的构造方法才能从外部new实例,不是吗?

2.那就是说要保证类的实例只有一个的话,这个类不能有公开的构造方法,对吧?

没错,就是这样,我们需要定义一个私有的构造方法

3.一个没有公开构造方法的类能够产生实例吗?如果构造方法是private,那么只有该类的实例才能调用这个构造方法,同样的要调用这个构造方法才能产生该类的实例。。这不是“鸡生蛋,蛋生鸡。。”的问题吗?

用私有的构造方法当然可以生产实例,上面忽略了一点:并不是“只有该类的实例才能调用这个构造方法”

因为在该类内部就可以随便调用这个私有的构造方法,并不需要创建任何实例

-------

有了上面的讨论结果,我们就可以实现经典的单件模式了:

package SingletonPattern;

/**
 * @author ayqy
 * 最经典的单件模式
 */
public class Singleton {

	private static Singleton instance;//定义静态实例变量

	/**
	 * 定义私有构造方法,防止从外部new实例
	 */
	private Singleton(){
		//初始化操作
	}

	/**
	 * 提供全局访问点
	 * @return 该类的实例
	 */
	public static Singleton getInstance(){
		if(instance == null)
			instance = new Singleton();
		return instance;
	}

	/*
	 * 其它有用的属性和行为
	 * 毕竟应用了单件模式的类仍然具有原本的功能
	 * */
}

注意:一定要清楚最后一点,应用了单件模式的类并不应该丧失其原本的功能,千万不能为了使用而使用

三.继续思考我们的单件模式

我们的单件模式已经万无一失了吗?

不,它还存在很多问题,比如:

1.多线程环境下

2.多个class loader环境下

我们无法保证产生的实例只有一个,对吧?

但是作为一种成熟的设计模式,单件模式必须要能从容应对这些环境,所以,接下来我们将讨论如何应对这些环境

四.多线程环境下的单件模式

如何在多线程环境下保证实例的唯一性?

很容易想到用synchronized关键字来保证线程安全,就像这样:

/**
 * 提供全局访问点
 * @return 该类的实例
 */
public static synchronized Singleton getInstance(){
	if(instance == null)
		instance = new Singleton();
	return instance;
}

我们把getInstance方法定义为同步方法就保证了不会有多个线程同时进入该方法,就不会产生不同的实例了

-------

但是上面的方法存在致命的问题:用synchronized关键字同步方法会极大的降低效率(同步一个方法甚至可能造成百倍的效率下降。。),这会拖垮我们的程序

有什么好的改进方法呢?

首先,上面的同步块是整个getIntance方法,每次调用该方法都会强制进入同步机制,但仔细一想,我们只在第一此调用该方法时需要进行同步(第一次new对象),之后的调用直接返回new好的对象就好了

那么,我们的改进方案就是:用双重加锁实现只在第一次new对象时进行同步

package SingletonPattern;

/**
 * @author ayqy
 * 多线程下的单件模式2——利用双重加锁保证只在实例化变量的时候进行同步
 */
public class DoubleLockSingleton {

	private static volatile DoubleLockSingleton instance;//定义静态实例变量

	/**
	 * 定义私有构造方法,防止从外部new实例
	 */
	private DoubleLockSingleton(){
		//初始化操作
	}

	/**
	 * 提供全局访问点
	 * @return 该类的实例
	 */
	public static DoubleLockSingleton getInstance(){
		if(instance == null)
			synchronized(DoubleLockSingleton.class){//进入同步块
				if(instance == null)//再次判空
					instance = new DoubleLockSingleton();
			}
		return instance;
	}

	/*
	 * 其它有用的属性和行为
	 * 毕竟应用了单件模式的类仍然具有原本的功能
	 * */
}

注意:双重加锁体现在volatile关键字(告诉编译器,这个变量不能被保留副本,一旦发生变动就会强制写回,避免了不一致)和synchronized修饰的同步块

但要明白这样做的代价,volatile关键字也会告诉编译器,不要对该对象进行编译优化

只看第一次new对象的过程的话,双重加锁的效率甚至要比同步方法更低,但在双重加锁方式在以后的调用中不再需要进行同步,所以长远看来双重加锁的效率要高于同步方法

-------

有没有一种方法不需要使用龟速的同步机制就能保证线程安全呢?如果有的话,绝对能够大大提高效率,对吧?

当然有,这种方法叫做“急切初始化”(顺便提一下,开篇提到的“经典单件模式”其实用了“延迟初始化”的方法。。很简单,不必解释),一起看看吧:

package SingletonPattern;

/**
 * @author ayqy
 * 多线程环境下的单件模式——用“急切初始化”来保证线程安全
 */
public class EagerlyInitSingleton {

	private static EagerlyInitSingleton instance = new EagerlyInitSingleton();//定义静态实例变量,并在类加载的时候就进行初始化操作

	/**
	 * 定义私有构造方法,防止从外部new实例
	 */
	private EagerlyInitSingleton(){
		//初始化操作
	}

	/**
	 * 提供全局访问点
	 * @return 该类的实例
	 */
	public static synchronized EagerlyInitSingleton getInstance(){
		return instance;
	}

	/*
	 * 其它有用的属性和行为
	 * 毕竟应用了单件模式的类仍然具有原本的功能
	 * */
}

额,这也能叫方法吗?这么做貌似不和标准吧?

没关系,这种方法自然有它的优点,比如:

1.效率很高,且线程安全

2.简单易用,什么都不用考虑,甚至不用判断

但其致命的缺点是:资源浪费问题,如果这个对象是一个巨大的极其耗费资源的对象,而我们在一开始就创建了它,却迟迟没有用到,这将是非常伤的。。

-------

上面提到了三种保证线程同步的方式,如何选择必须要结合具体情况来定,应综合考虑效率,资源利用等各个因素

五.多个class loader环境下的单件模式

如果存在多个类加载器,多个类加载器可能同时加载我们的单件类,从而产生多个实例

对于这种情况,我们可以显式指定使用哪一个class loader来加载单件类,这样就有效避免了上述问题

六.总结

应用单件模式可以保证对象的唯一性,但要注意单件模式的适用范围

不应该滥用单件模式,因为毕竟需要管理的资源敏感对象不会很多

时间: 2024-08-08 01:27:57

设计模式之单件模式(Singleton Pattern)的相关文章

设计模式 - 单件模式(singleton pattern) 详解

单件模式(singleton pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/28595349 单件模式(singleton pattern) : 确保一个类只有一个实例, 并提供一个全局访问点. 单价模式包括3个部分: 私有构造器, 静态变量, 静态方法. 具体方法: 1. 标准的单例模式: /** * @time 2014.6.5 */ package singleton; /** * @author

Design Patterns 乌蒙山连着山外山---单件模式singleton pattern

1 //包含单件实例的类Singleton 2 public class Singleton 3 { 4 //声明用于存储单件实例的变量instance 5 private static Singleton instance; 6 //定义用于标识同步线程的对象locker 7 private static Object locker = new Object(); 8 //私有的构造函数Singleton 9 private Singleton() { } 10 //公共访问的返回单件实例的函

1.单件模式(Singleton Pattern)

意图:为了保证一个类仅有一个实例,并提供一个访问它的全局访问点. 1.简单实现(多线程有可能产生多个实例) public class CommonSigleton { /// <summary> /// 私有的对象 /// </summary> private static CommonSigleton instance; /// <summary> /// 构造方法为Private /// </summary> private CommonSigleton

C#设计模式之一单例模式(Singleton Pattern)【创建型】

原文:C#设计模式之一单例模式(Singleton Pattern)[创建型] 一.引言 看了李建忠老师的讲的设计模式已经有一段时间了(这段时间大概有一年多了),自己还没有写过自己的.有关设计模式的文章.这次想写一些关于设计模式的文章,用自己的理解和代码来写,算是复习一遍.写作的过程中也会多看看其他大牛的文章,争取让自己的理解正确,否则把大家带跑偏了,就是我的过错了.今天就开始我们第一个设计模式,该模式是:[单例模式],英文名称:Singleton Pattern,这个模式很简单,一个类型只需要

设计模式(5)--单件模式

同步一个方法可能造成程序执行效率下降100倍. 静态初始化的控制权是在Java手上 一个类,一个责任 原则. ( 类应该做一件事,而且只做一件事)  但单件做了两件事, 一是 管理自己的实例(并提供全局访问变量) 二是在应用程序中担任角色, (1). 私有构造器.不提供对外访问   (2). 静态方法对外提供类本身实例.    (3)  . 只有一个实例 单例模式:  ( 管理共享资源 如数据库连接,或者线程池)  MVC  DAO Service 层 延迟实例化(懒汉式) 有线程安全问题  -

设计模式之单件模式

一.概述 /*    一般情况下,我们建立的一些类是属于工具性质的,基本不用存储太多的跟自身有关的数据,在这种情况下,每次都去new一个对象,即增加了开销,也使得代码更加臃肿.    其实,我们只需要一个实例对象就可以.如果采用全局或者静态变量的方式,会影响封装性,难以保证别的代码不会对全局变量造成影响.    考虑到这些需要,我们将默认的构造函数声明为私有的,这样就不会被外部所new了,甚至可以将析构函数也声明为私有的,这样就只有自己能够删除自己了.    在Java和C#这样纯的面向对象的语

设计模式 - 装饰者模式(Decorator Pattern) Java的IO类 使用方法

装饰者模式(Decorator Pattern) Java的IO类 使用方法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26716823 装饰者模式(decorator pattern)参见: http://blog.csdn.net/caroline_wendy/article/details/26707033 Java的IO类使用装饰者模式进行扩展, 其中FilterInputStream类, 就是装饰者(decora

如何让孩子爱上设计模式 ——10.桥接模式(Bridge Pattern)

如何让孩子爱上设计模式 --10.桥接模式(Bridge Pattern) 我有故事,你有酒吗?这年头写个技术文不讲个故事都不行,行,我讲: 还有发现很多的技术博文都开始有喜欢往文中插入几个表情的趋势了, 但是你真的插的姿势对了吗?这种事情不是随便插的,来来来,给你 见识下如何在适当的场景插入适当的表情以让读者感觉到易可赛艇, 本文以讲故事插表情为主,讲述桥接模式为辅,多图预警, 简书上排版可能有些问题,最佳排版可见: https://www.zybuluo.com/coder-pig/note

设计模式 - 装饰者模式(Decorator Pattern) 详解

装饰者模式(Decorator Pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26707033 装饰者模式(Decorator Pattern):动态地将责任附加到对象上. 若要扩展功能, 装饰者提供了比继承更有弹性的替代方案. 使用方法: 1. 首先创建组件(Component)父类, 所有类,具体组件(Concrete Component)和装饰者(Decorator)都属于这一类型, 可以进行扩展