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!
下载源码