java23种常用设计模式之单例模式(Singleton)

Singleton很有意思,也很容易理解,使用非常广泛。

在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样有几个好处:

1、某些类创建比较频繁,对于一些大型的对象,这可以节省一笔很大的系统开销。

2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

3、有些像交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了,只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

单例就是单一、独一无二的意思,比如每个人的DNA都是独一无二的,不可复制,每个人都是正版,除此之外还有什么不能山寨的呢?

下面是我在网上看到的一个很有意思的例子,很生动形象地说明了单例模式。

举个比较难复制的对象:皇帝

中国的历史上很少出现两个皇帝并存的时期,是有,但不多,那我们就认为皇帝是个单例模式,在这个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样(过渡期的不考虑,别找茬哦),大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,所以这个皇帝就是一个单例模式对象。

先看类图:

接着看代码,我先说的都在代码里了!

皇帝类:

package com.freedom.singleton1;
/**
 * 事先声明,本人爱扯,不喜欢看故事的请自动屏蔽//注释。
 * @author freedom
 * Emperor 皇帝类
 * 中国的历史上一般都是一个朝代一个皇帝,有两个的话,必然要PK出一个,所以亲们不要纠结PK期哦!
 */
public class Emperor {

	
	/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
	private static Emperor emperor = null; //定义一个皇帝,描述一下这个皇帝的特征
	
	/* 私有构造方法,防止被实例化 */  
	private Emperor() {
		//在地球人世俗和道德条条框框下,你是不允许产生第二个皇帝,否则生灵涂炭的灾难每日都在上演
		//所以注意构造方法是private而不是public
	}

	/* 静态工程方法,创建实例 */  
	public static Emperor getInstance() {
		if (emperor == null) { // 如果皇帝还没有定义,那就定一个
			emperor = new Emperor();
		}
		return emperor;
	}

	/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
	public static void emperorInfo() {
		// 描述皇帝的特征:比如长得帅不帅,高不高,后宫佳丽是三千还是三万,是政治天才还是祸国殃民的蠢才……
		System.out.println("名未知,字未来,眉清目秀,身高八尺,后宫佳丽三千万,个个能歌善武,出的了厅堂,上得了战场,所以你说是政治天才还是祸国殃民的蠢才呢?");
	}
}
大臣类:

package com.freedom.singleton1;
/**
 * 事先声明,本人爱扯,不喜欢看故事的请自动屏蔽注释。
 * @author freedom
 * Minister 大臣
 * 大臣天天要上早朝拜见皇帝,今天拜的皇帝和昨天、前天的都是一样的!
 * 请忽略今天和昨天不一样的情况,因为昨天的PK肯定试一次惨不忍睹的厮杀,
 * 在历史的长河中,平均发生的概率在1/365以下。
 * 在singleton2中通用的单例模式会解决同一朝代有两个皇帝,你怎么确定你每天拜见都是同一个皇帝问题。
 */
public class Minister {

	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.freedom.singleton2;
/**
 * @author freedom
 * SingletonPattern 通用单例模式
 * 
 */
public class SingletonPattern {
	private static SingletonPattern singletonPattern = null;

	// 私有构造方法,限制不能直接new产生一个实例
	private SingletonPattern() {
	}

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

来看看这一部分:

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

假设现在有两个线程A 和线程B,线程A 执行到 this.singletonPattern =new SingletonPattern(),

正在申请内存分配,可能需要0.001 微秒,就在这0.001 微秒之内,线程B 执行到if(this.singletonPattern == null),

你说这个时候这个判断条件是true 还是false?是true,那然后呢?

线程B 也往下走,于是乎就在内存中就有两个SingletonPattern 的实例了,看看是不是出问题了?

如果你这个单例是去拿一个序列号或者创建一个信号资源的时候,会怎么样?业务逻辑混乱!数据一致性校验失败!

最重要的是你从代码上还看不出什么问题,这才是最要命的!

因为这种情况基本上你是重现不了的,不寒而栗吧,那怎么修改?

有很多种方案,我就说一种,能简单粗暴地彻底解决问题的方案:

package com.freedom.singleton3;

/**
 * @author freedom 
 * SingletonPattern 通用单例模式
 */
public class SingletonPattern {

    private static final SingletonPattern singletonPattern = new SingletonPattern();

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

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

}

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

Excellent!

Finish!


下载源码







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值