java 两套锁_java 双重检查加锁弊端

Java是在语言级提供对线程的支持,所以Java的内存模型分为主存储器和工作存储器.

[Main memory]主存储器就是实例所在的存储区域,所有实例本身都被放在主存储器中,当然这

句话本身就说明了实例的字段也在主存储器中,主存储器被实例的所有线程所共有.

[working memory] 工作存储器当然就是每个线程所专有的工作区域,当然其中有它们共有的

主存储器中的一些必要的如实例字段等数据的COPY

当然你千万要知道Java是运行在虚拟系统上的,我们说的主存储器和工作存储器都是在物理内存

中虚拟出来的.是在JVM内部而言的.

在JLS中,对字段的存取被规定为read/write,use/assign,lock/unlock 六个最小的操作(action)

对于取而言,当一个线程一次访问实例字段时,首先从主存储器复制该字段的值到工作存储器,

然后线程引用该值.

但是如果同一线程多次访问该字段,是否每次都是从主存储器复制到工作存储器再引用,这由具体

的jvm环境决定.

简单说有可能不从主存储器中复制而直接引用工作区的COPY

同样对于存,如果一个线程一次赋值实例字段,那么会在工作存储器中进行,然后由jvm决定什么时

候映射到主存储器.

而同一个线程如果多次反复对实例字段赋值,那么有可能只对工作存储器的COPY进行,只把最后的

结果同步到主存储器,当然也有可能是每次都把工作存储器和主存储器同步的.这也由具体的JVM决

定.

所以如果多个线程反复对同一实例字段存取,就有可能一个线程对这个字段的改变没有及时反映给

其它线程,因为至少必须被从工作存储器同步到主存储器才能其它线程知道,而在线程专有的工作

存储器中的值其它线程是不可访问的.

所以boolean isInterrupted = false;这一句有可能一个线程中断后在这个线程工作的范围内已经

设为true,但还没有立即被映射到主存储器时,其它线程还不知道.

同样,对于double check,我们来看它的问题:

在java与模式一书中,作者这对个问题的说明根本没有正确地理解.而且他一再说明是错误的例子

其实是正确的.

public MyObject{

private static MyObect obj;

public static getInstance(){

if(obj == null){

synchronized(MyObj.class){

if(obj == null)

obj = new MyObject();

}

}

return obj;

}

}

这个例子根本不会存在问题.因为线程的同步保证了不会在同步块外部发生多线程调,而在同步块中

只有一个线程能执行,其赋值的结果在离开同步块时会强制映射到主工作区,也就是对obj的赋值一定

会在主工作区反映出来.所以作者举这个例子说明他根本没有理解为什么double check是不安全的.

我们来看下面的例子:

public MyObject{

private static MyObect obj;

private Date d = new Data();

public Data getD(){return this.d;}

public static MyObect  getInstance(){

if(obj == null){

synchronized(MyObect .class){

if(obj == null)

obj = new MyObject();//这里

}

}

return obj;

}

}

一个线程A运行到"这里"时,对于A的工作区中,肯定已经产生一个MyObect对象,而且这时这个对象已经

完成了Data d.现在线程A调用时间到,执行权被切换到另一个线程B来执行,会有什么问题呢?

如果obj不为null,线程B获得了一个obj,但可能obj.getD()却还没有初始化.

为什么?既然obj已经可见了(线程A还没有离开同步块),而d却不可见呢?

如果d不可见,那么obj也应该为null啊?线程B应该等待A释放同步块啊?

事实上,对于"这里"这条语句,线程A还没有离开同步块.

因为没有"离开同步块"这个条件,线程a的工作区没有强制与主存储器同步,这时工作区中有两个字段

obj,d 到底先把谁同步到主存储区,没有条件限制,虽然在线程A的工作区obj和d都是完整的,但有JSL

没有强制不允许先把obj映射到主存储区,如果哪个jvm实现按它的优化方案先把工作存储器中的obj

同步到主存储器了,这时正好线程B获取了,而d却没有同步过去,那么线程B就获取了obj的引用却找不能

obj.getD();

这个问题是否真的会发生?

我个人的意见,从理论上是会发生的,所以如果是设计银行交易系统你就没有必要为提高一些性能而放

弃安全性,而如果只是一般的应用,发生这种情况的机率本来就非常发生也不会有太多的危害,我认为

可以不去介意.比如获取数据库连结,即使获取不到也不过让访问者再刷新一次重新查询而已.事实上有

可能几天,几个月,一年都不会发生一次.知道小行星有可能会撞地球的,但你不要太在意而影响你正常

的生活.如果你不是做银行交易系统的你不要太担心,而大多数JVM实现本身就会保证这种安全的.也就

在把obj同步到主存储器会先同步d,但万一发生相反的顺序,你就无权责备你所用的JVM.只是这种万一

机会不是很多.

当然如果是贪婪式调用,静态实例的初始化由ClassLoader经由[Thread Safe]来完成,当然就不会有这

个问题了:

public MyObject{

private static MyObect obj = new MyObject();

private Date d = new Data();

public Data getD(){return this.d;}

public static getInstance(){

return obj;

}

}

那么如何保证实例字段能在工作存储区能被即时映,下一节我们来讲最不常用的关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值