★yesjoy★
★
总在爬山 所以艰辛;总在寻梦 所以苦痛
★
BlogJava
首页
新随笔
联系
聚合
管理
随笔 - 71 文章 - 15 trackbacks - 0
<
2024年11月
>
日
一
二
三
四
五
六
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
7
★
因为口渴,上帝创造了水;
★
因为黑暗,上帝创造了火;
★
因为我需要朋友,所以上帝让你来到我身边
╱◥█◣
|田|田|
╬╬╬╬╬╬╬╬╬╬╬
If only I have such a house!
〖总在爬山 所以艰辛〗
Email:myesjoy@yahoo.com.cn
NickName:yesjoy
MSN:myesjoy@hotmail.com
QQ:150230516
〖总在寻梦 所以苦痛〗
常用链接
我的随笔
我的评论
我的参与
最新评论
留言簿
(3)
给我留言
查看公开留言
查看私人留言
随笔分类
Hibernate学习总结(1)
J2EE架构
Struts学习总结(1)
随笔档案
2018年8月 (1)
2007年6月 (3)
2007年5月 (8)
2007年4月 (21)
2007年3月 (6)
2007年1月 (1)
2006年12月 (3)
2006年8月 (1)
2006年4月 (1)
2006年3月 (1)
文章分类
AOP(面向方面编程)(5)
C/C++语言算法总结(16)
CORBA 学习(1)
DB2学习(1)
Hibernate学习(8)
J2EE结构(3)
java.applet包(2)
java.awt包(4)
java.util包 (2)
JAVA代码查错(2)
Java基础知识(3)
Java常用类
JAVA编程规范(2)
Oracle学习(3)
PowerDesigner设计(4)
Spring学习(3)
Structs学习(15)
公众资源(1)
存储设备(1)
工作流基本知识(6)
常用Jar包介绍和应用(1)
数据库备份/恢复方案(2)
数据库的查询及性能优化(1)
数据库设计(4)
数据结构(1)
算法总结(7)
设计模式(4)
读书世界(2)
软件工程学(13)
需求管理(1)
项目经验总结(2)
文章档案
2014年1月 (1)
2007年8月 (10)
2007年7月 (7)
2007年6月 (19)
2007年5月 (9)
2007年4月 (3)
2007年3月 (5)
2007年1月 (3)
2006年11月 (7)
2006年10月 (6)
2006年8月 (5)
2006年7月 (1)
2006年4月 (5)
2006年3月 (7)
2006年2月 (20)
2006年1月 (14)
Hibernate在线
HIBERNATE - 符合Java习惯的关系数据库持久化
Java友情
§BlogJava - 铁手剑谱§
§oksonic博客§
§sampa : colin’s blog§
§人生代码§
Java认证
IT认证传奇站
JAVA SCJD认证 SCJP认证 JAVA程序设计员认证网站
linux经典
‖LinuxABC.NET‖
Linux菜鸟到高手,就在LinuxABC.NET
OA系统
¤易能协同办公系统¤
流程管理、知识管理、客户关系管理、辅助办公
¤黄城网络办公系统3.0¤
B/S结构,适用于Intranet/Internet应用,实现无地域限制的全球办公,具有邮件管理、业务管理、网络硬盘、智能工作流等功能。
Spring在线
【Spring Framework 开发参考手册】
Structs在线
【IBM_Struts 应用专题】
【Structs官方网站】
专家专栏
§张孝祥专栏_Java基础§
§芮祥麟的专栏_SOA§
企业信息化
¢ERPHome¢
¢e-works中国制造业信息化门户¢
管理_技术_信息化
大型设备共享系统
武汉大学仪器设备共享平台
湖南高校大型仪器设备共用网
工作流
∫中国工作流论坛∫
∫工作流管理联盟WfMC ∫
∫工作流网∫
业务过程建模与工作流管理技术论坛
工作流产品
℃协同软件开发者社区 :: 首页℃
网上购书
China-Pub网上书店
卓越网: 网上购物
当当网,全球最大的中文网上商城
淘宝网
搜索
最新评论
1. re: DetachedCriteria关联查询
dddd
--sss
2. re: Struts提供的强大的HTML标签库总结[未登录]
是
--哈哈
3. re: 打印出1900~2000年中所有的闰年
不错
--网通
4. re: DetachedCriteria关联查询
@name
是有重复数据,该怎么解决啊
--996416660
5. re: DetachedCriteria关联查询
这个查询的话,会多出重复数据。
--name
阅读排行榜
1. 在struts中如何使用showModalDialog() (1028)
2. eclipse中进行hibernate映射的步骤(846)
3. 2006年12月份工作总结之一(598)
4. 这五天来的工作。。。(402)
5. 篮球的意义(389)
评论排行榜
1. 十多年后再发个随笔,感觉很奇怪(0)
2. 买了移动硬盘(0)
3. 这五天来的工作。。。(0)
4. 高效工作,高效生活,我的人生!(0)
5. 安全意识在哪里?(0)
单例模式完全剖析(2)---- 探究简单却又使人迷惑的单例模式
测试单例模式
接下来,我使用与log4j相对应的JUnit来测试单例类,它会贯穿在这篇文章余下的部分。如果你对JUnit或log4j不很熟悉,请参考相关资源。
例2是一个用JUnit测试例1的单例模式的案例:
例2.一个单例模式的案例
import
org.apache.log4j.
Logger
;
import
junit.framework.
Assert
;
import
junit.framework.
TestCase
;
public
class
SingletonTest
extends
TestCase
{
private
ClassicSingleton sone =
null
, stwo =
null
;
private
static
Logger
logger =
Logger
.getRootLogger();
public
SingletonTest(
String
name) {
super
(name);
}
public
void
setUp() {
logger.info(
"getting singleton..."
);
sone = ClassicSingleton.getInstance();
logger.info(
"...got singleton: "
+ sone);
logger.info(
"getting singleton..."
);
stwo = ClassicSingleton.getInstance();
logger.info(
"...got singleton: "
+ stwo);
}
public
void
testUnique() {
logger.info(
"checking singletons for equality"
);
Assert
.assertEquals(
true
, sone == stwo);
}
}
例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出:
例3.是这个测试案例的输出
Buildfile: build.xml
init:
[echo] Build 20030414 (14-04-2003 03:08)
compile:
run-test-text:
[java] .INFO main: [b]getting singleton...[/b]
[java] INFO main: [b]created singleton:[/b] Singleton@e86f41
[java] INFO main: ...got singleton: Singleton@e86f41
[java] INFO main: [b]getting singleton...[/b]
[java] INFO main: ...got singleton: Singleton@e86f41
[java] INFO main: checking singletons
for
equality
[java]
Time
: 0.032
[java] OK (1 test)
正如前面的清单所示,例2的简单测试顺利通过----通过ClassicSingleton.getInstance()获得的两个单例类的引用确实相同;然而,你要知道这些引用是在单线程中得到的。下面的部分着重于用多线程测试单例类。
多线程因素的考虑
在例1中的ClassicSingleton.getInstance()方法由于下面的代码而不是线程安全的:
1:
if
(instance ==
null
) {
2: instance =
new
Singleton();
3: }
如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现(译注:在这可能是作者对很少出现这种情况而导致无法测试从而使人们放松警惕而感到叹惜)。为了演示这个线程轮换,我得重新实现例1中的那个类。例4就是修订后的单例类:
例4.人为安排的方式
import
org.apache.log4j.
Logger
;
public
class
Singleton {
private
static
Singleton singleton =
null
;
private
static
Logger
logger =
Logger
.getRootLogger();
private
static
boolean
firstThread =
true
;
protected
Singleton() {
// Exists only to defeat instantiation.
}
public
static
Singleton getInstance() {
if
(singleton ==
null
) {
simulateRandomActivity();
singleton =
new
Singleton();
}
logger.info(
"created singleton: "
+ singleton);
return
singleton;
}
private
static
void
simulateRandomActivity() {
try
{
if
(firstThread) {
firstThread =
false
;
logger.info(
"sleeping..."
);
// This nap should give the second thread enough time
// to get by the first thread.
Thread
.currentThread().sleep(50);
}
}
catch
(
InterruptedException
ex) {
logger.warn(
"Sleep interrupted"
);
}
}
}
除了在这个清单中的单例类强制使用了一个多线程错误处理,例4类似于例1中的单例类。在getInstance()方法第一次被调用时,调用这个方法的线程会休眠50毫秒以便另外的线程也有时间调用getInstance()并创建一个新的单例类实例。当休眠的线程觉醒时,它也会创建一个新的单例类实例,这样我们就有两个单例类实例。尽管例4是人为如此的,但它却模拟了第一个线程调用了getInstance()并在没有完成时被切换的真实情形。
例5测试了例4的单例类:
例5.失败的测试
import
org.apache.log4j.
Logger
;
import
junit.framework.
Assert
;
import
junit.framework.
TestCase
;
public
class
SingletonTest
extends
TestCase
{
private
static
Logger
logger =
Logger
.getRootLogger();
private
static
Singleton singleton =
null
;
public
SingletonTest(
String
name) {
super
(name);
}
public
void
setUp() {
singleton =
null
;
}
public
void
testUnique()
throws
InterruptedException
{
// Both threads call Singleton.getInstance().
Thread
threadOne =
new
Thread
(
new
SingletonTestRunnable()),
threadTwo =
new
Thread
(
new
SingletonTestRunnable());
threadOne.start();
threadTwo.start();
threadOne.join();
threadTwo.join();
}
private
static
class
SingletonTestRunnable
implements
Runnable
{
public
void
run() {
// Get a reference to the singleton.
Singleton s = Singleton.getInstance();
// Protect singleton member variable from
// multithreaded access.
synchronized
(SingletonTest.
class
) {
if
(singleton ==
null
)
// If local reference is null...
singleton = s;
// ...set it to the singleton
}
// Local reference must be equal to the one and
// only instance of Singleton; otherwise, we have two
// Singleton instances.
Assert
.assertEquals(
true
, s == singleton);
}
}
}
例5的测试案例创建两个线程,然后各自启动,等待完成。这个案例保持了一个对单例类的静态引用,每个线程都会调用Singleton.getInstance()。如果这个静态成员变量没有被设置,那么第一个线程就会将它设为通过调用getInstance()而得到的引用,然后这个静态变量会与一个局部变量比较是否相等。
在这个测试案例运行时会发生一系列的事情:第一个线程调用getInstance(),进入if块,然后休眠;接着,第二个线程也调用getInstance()并且创建了一个单例类的实例。第二个线程会设置这个静态成员变量为它所创建的引用。第二个线程检查这个静态成员变量与一个局部备份的相等性。然后测试通过。当第一个线程觉醒时,它也会创建一个单例类的实例,并且它不会设置那个静态成员变量(因为第二个线程已经设置过了),所以那个静态变量与那个局部变量脱离同步,相等性测试即告失败。例6列出了例5的输出:
例6.例5的输出
Buildfile: build.xml
init:
[echo] Build 20030414 (14-04-2003 03:06)
compile:
run-test-text:
INFO
Thread
-1: sleeping...
INFO
Thread
-2: created singleton: Singleton@7e5cbd
INFO
Thread
-1: created singleton: Singleton@704ebb
junit.framework.
AssertionFailedError
: expected: but was:
at junit.framework.
Assert
.fail(Assert.java:47)
at junit.framework.
Assert
.failNotEquals(Assert.java:282)
at junit.framework.
Assert
.assertEquals(Assert.java:64)
at junit.framework.
Assert
.assertEquals(Assert.java:149)
at junit.framework.
Assert
.assertEquals(Assert.java:155)
at SingletonTest$SingletonTestRunnable.run(Unknown
Source
)
at java.lang.
Thread
.run(
Thread
.java:554)
[java] .
[java]
Time
: 0.577
[java] OK (1 test)
到现在为止我们已经知道例4不是线程安全的,那就让我们看看如何修正它。
同步
要使例4的单例类为线程安全的很容易----只要像下面一个同步化getInstance()方法:
public
synchronized
static
Singleton getInstance() {
if
(singleton ==
null
) {
simulateRandomActivity();
singleton =
new
Singleton();
}
logger.info(
"created singleton: "
+ singleton);
return
singleton;
}
在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果:
Buildfile: build.xml
init:
[echo] Build 20030414 (14-04-2003 03:15)
compile:
[javac] Compiling 2 source files
run-test-text:
INFO
Thread
-1: sleeping...
INFO
Thread
-1: created singleton: Singleton@ef577d
INFO
Thread
-2: created singleton: Singleton@ef577d
[java] .
[java]
Time
: 0.513
[java] OK (1 test)
这此,这个测试案例工作正常,并且多线程的烦恼也被解决;然而,机敏的读者可能会认识到getInstance()方法只需要在第一次被调用时同步。因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。
一种性能改进的方法
寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:
public
static
Singleton getInstance() {
if
(singleton ==
null
) {
synchronized
(Singleton.
class
) {
singleton =
new
Singleton();
}
}
return
singleton;
}
这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。
双重加锁检查
初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:
public
static
Singleton getInstance() {
if
(singleton ==
null
) {
synchronized
(Singleton.
class
) {
if
(singleton ==
null
) {
singleton =
new
Singleton();
}
}
}
return
singleton;
}
如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。
不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。
一个改进的线程安全的单例模式实现
例7列出了一个简单、快速而又是线程安全的单例模式实现:
例7.一个简单的单例类
public
class
Singleton {
public
final
static
Singleton INSTANCE =
new
Singleton();
private
Singleton() {
// Exists only to defeat instantiation.
}
}
这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:
Singleton singleton = Singleton.INSTANCE;
singleton.dothis();
singleton.dothat();
...
当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.
你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类.
posted on 2006-02-14 15:33
★yesjoy★
阅读(218)
评论(0)
编辑
收藏
所属分类:
设计模式
新用户注册
刷新评论列表
只有注册用户
登录
后才能发表评论。
网站导航:
博客园
IT新闻
知识库
C++博客
博问
管理
相关文章:
单例模式完全剖析(2)---- 探究简单却又使人迷惑的单例模式
单例模式完全剖析(1)---- 探究简单却又使人迷惑的单例模式
java23种模式一点就通
设计模式的基础