java 双重检查加锁弊端
http://blog.csdn.net/axman/article/details/1089196
Java是在語言級(jí)提供對(duì)線程的支持,所以Java的內(nèi)存模型分為主存儲(chǔ)器和工作存儲(chǔ)器.
[Main?memory]主存儲(chǔ)器就是實(shí)例所在的存儲(chǔ)區(qū)域,所有實(shí)例本身都被放在主存儲(chǔ)器中,當(dāng)然這
句話本身就說明了實(shí)例的字段也在主存儲(chǔ)器中,主存儲(chǔ)器被實(shí)例的所有線程所共有.
[working?memory]?工作存儲(chǔ)器當(dāng)然就是每個(gè)線程所專有的工作區(qū)域,當(dāng)然其中有它們共有的
主存儲(chǔ)器中的一些必要的如實(shí)例字段等數(shù)據(jù)的COPY
當(dāng)然你千萬要知道Java是運(yùn)行在虛擬系統(tǒng)上的,我們說的主存儲(chǔ)器和工作存儲(chǔ)器都是在物理內(nèi)存
中虛擬出來的.是在JVM內(nèi)部而言的.
在JLS中,對(duì)字段的存取被規(guī)定為read/write,use/assign,lock/unlock?六個(gè)最小的操作(action)
對(duì)于取而言,當(dāng)一個(gè)線程一次訪問實(shí)例字段時(shí),首先從主存儲(chǔ)器復(fù)制該字段的值到工作存儲(chǔ)器,
然后線程引用該值.
但是如果同一線程多次訪問該字段,是否每次都是從主存儲(chǔ)器復(fù)制到工作存儲(chǔ)器再引用,這由具體
的jvm環(huán)境決定.
簡(jiǎn)單說有可能不從主存儲(chǔ)器中復(fù)制而直接引用工作區(qū)的COPY
同樣對(duì)于存,如果一個(gè)線程一次賦值實(shí)例字段,那么會(huì)在工作存儲(chǔ)器中進(jìn)行,然后由jvm決定什么時(shí)
候映射到主存儲(chǔ)器.
而同一個(gè)線程如果多次反復(fù)對(duì)實(shí)例字段賦值,那么有可能只對(duì)工作存儲(chǔ)器的COPY進(jìn)行,只把最后的
結(jié)果同步到主存儲(chǔ)器,當(dāng)然也有可能是每次都把工作存儲(chǔ)器和主存儲(chǔ)器同步的.這也由具體的JVM決
定.
所以如果多個(gè)線程反復(fù)對(duì)同一實(shí)例字段存取,就有可能一個(gè)線程對(duì)這個(gè)字段的改變沒有及時(shí)反映給
其它線程,因?yàn)橹辽俦仨毐粡墓ぷ鞔鎯?chǔ)器同步到主存儲(chǔ)器才能其它線程知道,而在線程專有的工作
存儲(chǔ)器中的值其它線程是不可訪問的.
所以boolean?isInterrupted?=?false;這一句有可能一個(gè)線程中斷后在這個(gè)線程工作的范圍內(nèi)已經(jīng)
設(shè)為true,但還沒有立即被映射到主存儲(chǔ)器時(shí),其它線程還不知道.
同樣,對(duì)于double?check,我們來看它的問題:
在java與模式一書中,作者這對(duì)個(gè)問題的說明根本沒有正確地理解.而且他一再說明是錯(cuò)誤的例子
其實(shí)是正確的.
public?MyObject{
????private?static?MyObect?obj;
????public?static?getInstance(){
????????if(obj?==?null){
????????????synchronized(MyObj.class){
????????????????if(obj?==?null)
????????????????????obj?=?new?MyObject();
????????????}
????????}
????????return?obj;
????}
}
這個(gè)例子根本不會(huì)存在問題.因?yàn)榫€程的同步保證了不會(huì)在同步塊外部發(fā)生多線程調(diào),而在同步塊中
只有一個(gè)線程能執(zhí)行,其賦值的結(jié)果在離開同步塊時(shí)會(huì)強(qiáng)制映射到主工作區(qū),也就是對(duì)obj的賦值一定
會(huì)在主工作區(qū)反映出來.所以作者舉這個(gè)例子說明他根本沒有理解為什么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;
????}
}
一個(gè)線程A運(yùn)行到"這里"時(shí),對(duì)于A的工作區(qū)中,肯定已經(jīng)產(chǎn)生一個(gè)MyObect對(duì)象,而且這時(shí)這個(gè)對(duì)象已經(jīng)
完成了Data?d.現(xiàn)在線程A調(diào)用時(shí)間到,執(zhí)行權(quán)被切換到另一個(gè)線程B來執(zhí)行,會(huì)有什么問題呢?
如果obj不為null,線程B獲得了一個(gè)obj,但可能obj.getD()卻還沒有初始化.
為什么?既然obj已經(jīng)可見了(線程A還沒有離開同步塊),而d卻不可見呢?
如果d不可見,那么obj也應(yīng)該為null啊?線程B應(yīng)該等待A釋放同步塊啊?
事實(shí)上,對(duì)于"這里"這條語句,線程A還沒有離開同步塊.
因?yàn)闆]有"離開同步塊"這個(gè)條件,線程a的工作區(qū)沒有強(qiáng)制與主存儲(chǔ)器同步,這時(shí)工作區(qū)中有兩個(gè)字段
obj,d?到底先把誰同步到主存儲(chǔ)區(qū),沒有條件限制,雖然在線程A的工作區(qū)obj和d都是完整的,但有JSL
沒有強(qiáng)制不允許先把obj映射到主存儲(chǔ)區(qū),如果哪個(gè)jvm實(shí)現(xiàn)按它的優(yōu)化方案先把工作存儲(chǔ)器中的obj
同步到主存儲(chǔ)器了,這時(shí)正好線程B獲取了,而d卻沒有同步過去,那么線程B就獲取了obj的引用卻找不能
obj.getD();
這個(gè)問題是否真的會(huì)發(fā)生?
我個(gè)人的意見,從理論上是會(huì)發(fā)生的,所以如果是設(shè)計(jì)銀行交易系統(tǒng)你就沒有必要為提高一些性能而放
棄安全性,而如果只是一般的應(yīng)用,發(fā)生這種情況的機(jī)率本來就非常發(fā)生也不會(huì)有太多的危害,我認(rèn)為
可以不去介意.比如獲取數(shù)據(jù)庫連結(jié),即使獲取不到也不過讓訪問者再刷新一次重新查詢而已.事實(shí)上有
可能幾天,幾個(gè)月,一年都不會(huì)發(fā)生一次.知道小行星有可能會(huì)撞地球的,但你不要太在意而影響你正常
的生活.如果你不是做銀行交易系統(tǒng)的你不要太擔(dān)心,而大多數(shù)JVM實(shí)現(xiàn)本身就會(huì)保證這種安全的.也就?
在把obj同步到主存儲(chǔ)器會(huì)先同步d,但萬一發(fā)生相反的順序,你就無權(quán)責(zé)備你所用的JVM.只是這種萬一
機(jī)會(huì)不是很多.
當(dāng)然如果是貪婪式調(diào)用,靜態(tài)實(shí)例的初始化由ClassLoader經(jīng)由[Thread?Safe]來完成,當(dāng)然就不會(huì)有這
個(gè)問題了:
public?MyObject{
????private?static?MyObect?obj?=?new?MyObject();
????private?Date?d?=?new?Data();
????public?Data?getD(){return?this.d;}
????public?static?getInstance(){
????????return?obj;
????}
}
那么如何保證實(shí)例字段能在工作存儲(chǔ)區(qū)能被即時(shí)映,下一節(jié)我們來講最不常用的關(guān)鍵字
?
對(duì)于涉及對(duì)象初始化的DCL,從JAVA1.5以后雖然可以用volatile來修補(bǔ),但已經(jīng)沒有任何意義。因?yàn)?/p>
?
比它更好的模式lazy initialization hoder可以達(dá)到相同的作用而更容易理解:
?
?
?
public MyObject{
?
?
?
??? private static class instanceHoder{//內(nèi)部私有的類,我特別用了小寫開頭。
?
??????? static MyObject instance = new MyObject();
?
??? }
??? private static MyObect obj;
??? private Date d = new Data();
??? public Data getD(){return this.d;}
??? public static MyObect? getInstance(){
?
???????????return instanceHoder.instance;
??? }
}
?
?
?
private static class instanceHoder是類的定義并不會(huì)引起初始化。只有在首次調(diào)用getInstance時(shí)才會(huì)加載
?
instanceHoder類然后初始化instance實(shí)例。而靜態(tài)初始化是由JVM來保證線程安全的,所以整個(gè)過程都不需要同
?
步參與,極大地提高了性能。
轉(zhuǎn)載于:https://my.oschina.net/vshcxl/blog/876852
總結(jié)
以上是生活随笔為你收集整理的java 双重检查加锁弊端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sqlserver 人名_sqlserv
- 下一篇: mysql的含义及特点_MySQL——基