单例模式Singleton——读书笔记

本文探讨了在Java中实现线程安全的单例模式的不同方法。从同步方法到双重检查锁定,最后介绍了一个简洁高效的静态初始化单例实现。文中详细分析了各种实现方式的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[b]同步[/b]

线程安全的例子----只要像下面一个同步化getInstance()方法:
Java代码

public synchronized static Singleton getInstance() { 
if(singleton == null) {
singleton = new Singleton();
}
logger.info("created singleton: " + singleton);
return singleton;
}



在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果:
Java代码

1. Buildfile: build.xml
2.
3. init:
4. [echo] Build 20030414 (14-04-2003 03:15)
5.
6. compile:
7. [javac] Compiling 2 source files
8.
9. run-test-text:
10. INFO Thread-1: sleeping...
11. INFO Thread-1: created singleton: Singleton@ef577d
12. INFO Thread-2: created singleton: Singleton@ef577d
13. [java] .
14. [java] Time: 0.513
15.
16. [java] OK (1 test)

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()方法中的赋值语句。

[b]一种性能改进的方法[/b]

寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:
Java代码

public static Singleton getInstance() { 
if(singleton == null) {
synchronized(Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}


这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给 singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。

[b]双重加锁检查[/b]

初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:
Java代码

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就会被返回一个对未初始化的单例类实例的引用。

[b]一个改进的线程安全的单例模式实现[/b]

例7列出了一个简单、快速而又是线程安全的单例模式实现:
例7.一个简单的单例类
Java代码

public class Singleton { 
public final static Singleton INSTANCE = new Singleton();
private Singleton() {
// Exists only to defeat instantiation.
}
}


这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:
Java代码

1. Singleton singleton = Singleton.INSTANCE;
2. singleton.dothis();
3. singleton.dothat();
4. ...

Singleton singleton = Singleton.INSTANCE;
singleton.dothis();
singleton.dothat();
...

当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.

参考:单例模式完全剖析 http://www.iteye.com/topic/60179
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值