学点设计模式-单例模式

单例模式

这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,
单例就是单一、独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?
我们举个比较难复制的对象:皇帝
中国的历史上很少出现两个皇帝并存的时期,是有,但不多,那我们就认为皇帝是个单例模式,在这
个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样(过
渡期的不考虑,别找茬哦),大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,单例模式,绝对的单例模式,

先看类图:

package com.cbf4life.singleton1;

public class Emperor {
	
	private static  Emperor emperor=null;//定义一个皇帝放在那里,然后给这个皇帝名字
	private Emperor(){
		//世俗和道德约束你,目的就是不让你产生第二个皇帝
	}
	public static Emperor getInstance(){
		
		if(emperor==null){
			return new Emperor();
		}
		return emperor;
	}
	//皇帝叫什么名字呀
	public static void emperorInfo(){
		System.out.println("我就是皇帝某某某....");
	}
}


 

package com.cbf4life.singleton1;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 大臣是天天要面见皇帝,今天见的皇帝和昨天的,前天不一样那就出问题了!
*/
public class Minister {
	
	@SuppressWarnings("static-access")
	public static void main(String[] args) {
		
		//第一天
		Emperor emperor1=Emperor.getInstance();
		emperor1.emperorInfo(); //第一天见的皇帝叫什么名字呢?
		//第二天
		Emperor emperor2=Emperor.getInstance();
		Emperor.emperorInfo();
		//第三天
		Emperor emperor3=Emperor.getInstance();
		emperor2.emperorInfo();
		//三天见的皇帝都是同一个人,荣幸吧!
		
	}
}


看到没,大臣天天见到的都是同一个皇帝,不会产生错乱情况,反正都是一个皇帝,是好是坏就这一
个,只要提到皇帝,大家都知道指的是谁,清晰,而又明确。问题是这是通常情况,还有个例的,如同一
个时期同一个朝代有两个皇帝,怎么办?
单例模式很简单,就是在构造函数中多了加一个构造函数,访问权限是private 的就可以了,这个模
式是简单,但是简单中透着风险,风险?什么风险?在一个B/S 项目中,每个HTTP Request 请求到J2EE
的容器上后都创建了一个线程,每个线程都要创建同一个单例对象,怎么办?,好,我们写一个通用的单例程
序,然后分析一下:

package com.cbf4life.singleton1;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 通用单例模式
 */
@SuppressWarnings("all")
public class SingletonPattern {
	private static SingletonPattern singletonPattern = null;

	// 限制住不能直接产生一个实例
	private SingletonPattern() {
	}

	public SingletonPattern getInstance() {
		if (this.singletonPattern == null) { // 如果还没有实例,则创建一个
			this.singletonPattern = new SingletonPattern();
		}
		return this.singletonPattern;
	}
}


        我们来看判断的那一部分,假如现在有两个线程A 和线程B,线程A 执行到 this.singletonPattern =
new SingletonPattern(),正在申请内存分配,可能需要0.001 微秒,就在这0.001 微秒之内,线程B 执
行到if(this.singletonPattern == null),你说这个时候这个判断条件是true 还是false?是true,那
然后呢?线程B 也往下走,于是乎就在内存中就有两个SingletonPattern 的实例了,看看是不是出问题了?
        如果你这个单例是去拿一个序列号或者创建一个信号资源的时候,会怎么样?业务逻辑混乱!数据一致性
校验失败!最重要的是你从代码上还看不出什么问题,这才是最要命的!因为这种情况基本上你是重现不
了的,不寒而栗吧,那怎么修改?有很多种方案,我就说一种,能简单的、彻底解决问题的方案

package com.cbf4life.singleton1;

/**
 * @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
 *         all. 通用单例模式
 */
@SuppressWarnings("all")
public class SingletonPattern2 {
	private static final SingletonPattern2 singletonPattern = new SingletonPattern2();

	// 限制住不能直接产生一个实例
	private SingletonPattern2() {
	}

	public synchronized static SingletonPattern2 getInstance() {
		return singletonPattern;
	}
}


直接new 一个对象传递给类的成员变量singletonpattern,你要的时候getInstance()直接返回给
你,解决问题!

 

 

定义与结构
单例模式又叫做单态模式或者单件模式。在 GOF 书中给出的定义为:保证一个类仅有
一个实例,并提供一个访问它的全局访问点。单例模式中的“单例”通常用来代表那些本质上
具有唯一性的系统组件(或者叫做资源)。比如文件系统、资源管理器等等。
单例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的
改变对象的个数。那么怎么来实现单例模式呢?一个类的对象的产生是由类构造函数来完成
的,如果想限制对象的产生,一个办法就是将构造函数变为私有的(至少是受保护的),使
得外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必须提供一个自己的对
象以及访问这个对象的静态方法。
现在对单例模式有了大概的了解了吧,其实单例模式在实现上是非常简单的——只有一
个角色,而客户则通过调用类方法来得到类的对象。

单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象,多
个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是
不变单例对象,仅用做提供工具函数。

单例模式邪恶论
看这题目也许有点夸张,不过这对初学者是一个很好的警告。单例模式在 java 中的使
用存在很多陷阱和假象,这使得没有意识到单例模式使用局限性的你在系统中布下了隐
患……
单例模式在java 中存在的陷阱。
多个虚拟机
当系统中的单例类被拷贝运行在多个虚拟机下的时候,在每一个虚拟机下都可以创建一
个实例对象。在使用了EJB、JINI、RMI 技术的分布式系统中,由于中间件屏蔽掉了分布式
系统在物理上的差异,所以对你来说,想知道具体哪个虚拟机下运行着哪个单例对象是很困
难的。
因此,在使用以上分布技术的系统中,应该避免使用存在状态的单例模式,因为一个有
状态的单例类,在不同虚拟机上,各个单例对象保存的状态很可能是不一样的,问题也就随
之产生。而且在EJB 中不要使用单例模式来控制访问资源,因为这是由EJB 容器来负责的。
在其它的分布式系统中,当每一个虚拟机中的资源是不同的时候,可以考虑使用单例模式来
进行管理。
多个类加载器
当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字
节都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间
(namespace)来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。
也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少
见。在很多J2EE 服务器上允许存在多个servlet 引擎,而每个引擎是采用不同的类加载器的;
浏览器中applet 小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,
等等。
这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一
般情况下不要使用存在状态的单例模式。
错误的同步处理
在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能
会达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定
要对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。
子类破坏了对象控制
在上一节介绍最后一种扩展性较好的单例模式实现方式的时候,就提到,由于类构造函
数变得不再私有,就有可能失去对对象的控制。这种情况只能通过良好的文档来规范。
串行化(可序列化)
为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不
够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是
一个对原有对象的引用。为了防止这种情况,可以在单例类中加入readResolve 方法。关于
这个方法的具体情况请参考《Effective Java》一书第57 条建议。
其实对象的串行化并不仅局限于上述方式,还存在基于 XML 格式的对象串行化方式。
这种方式也存在上述的问题,所以在使用的时候要格外小心。
上面罗列了一些使用单例模式时可能会遇到的问题。而且这些问题都和java 中的类、
线程、虚拟机等基础而又复杂的概念交织在一起,你如果稍不留神……。但是这并不代表着
单例模式就一无是处,更不能一棒子将其打死。它还是不可缺少的一种基础设计模式,它对
一些问题提供了非常有效的解决方案,在java 中你完全可以把它看成编码规范来学习,只
是使用的时候要考虑周全些就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值