同步锁的失败可能

以下例子参考[url]http://developer.51cto.com/art/201104/256239.htm[/url]

网上闲逛时发现一篇博文,讲的是单例同步锁时失败的可能,提到的错误自己基本都忽略了,下面以其中的例子说一下自己的理解。

单例模式是比较简单直接的:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {
instance = new Singleton();
}

return instance;
}
}

这也是在初学单例模式时会提到的一个采用延迟加载实现的例子,实际上这个例子并不能“保证”单例,在多线程高并发的情况下,如果瞬间同时初始访问getInstance方法,返回的可能就是不同的实例。于是就有了下面的简单的同步机制:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public synchronized static Singleton getInstance() {

if(instance == null) {
instance = new Singleton();
}

return instance;
}
}

这种方式保证了线程安全,但是我们只需要在生成实例的过程中保证其同步即可,不需要对访问也进行加锁。很明显,简单的在类实例范围内对其加锁,性能是其瓶颈。为了避免这种情况,便会有下面的想法:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {

synchronized(Singleton.class) {

if(instance == null) {

instance = new Singleton();

}
}
}

return instance;
}
}

其实这样便可以保证单例了,但是却保证不了单例的准确性。也就是说不同的线程可以拿到同一个实例,但是在某些情况下,会取到实例的错误状态。在本例中,初始化Singleton对象和将对象地址赋给instance的顺序是不确定的。也即可能有以下两种情况:
1、在初始化之前将对象引用赋给instance。这时,instance有了实际的引用,但对象还没有初始化。其他线程此时如果获取到instance,可能就是只有引用而还没有初始化的实例。
2、在初始化之后将对象引用赋给instance。这是正确的状态,保证了单例也保证了正确性。

这种不确定性不能“保证”单例的正确性。再看下面的改进方案:
public class Singleton {  

private static Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {

Singleton temp;

synchronized(Singleton.class) {

temp = instance;

if(temp == null) {

synchronized(Singleton.class) {

temp = new Singleton();

}
instance = temp;
}
}
}

return instance;
}
}

这种方式制造了一个内存屏障,即使用了一个临时变量来保证“初始化操作和赋引用操作”的原子性。这种方式从代码方面看已经没有问题了,但是注意到在同步语句块之外的instance=temp,根据博文的解释:[color=red]同步语句块内的操作必须在语句块结束之前完成,但是代码中同步块之外的操作有可能被编译器放到块内执行(只是存在这种可能性)。[/color]这一点细节确实不容易考虑到,需要了解一些JVM的知识,这方面没什么研究,保留意见。
看最后一种方式:
public class Singleton {  

private static volatile Singleton instance = null;

private Singleton(){}

public static Singleton getInstance() {

if(instance == null) {

synchronized(Singleton.class) {

if(instance == null) {

instance = new Singleton();

}
}
}
return instance;
}
}

这里要说一下volatile关键字,这个除了在一定程度上确保同步之外,JDK1.5扩充了volatile语义,简单点说就是保证了对象初始化以及赋引用的有序性,在本例来说就是先初始化后赋引用。这样两层机制保证了单例的唯一性和准确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值