亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Java線程安全兼談DCL

系統(tǒng) 2395 0

轉(zhuǎn)載自 ---- http://www.iteye.com/topic/875420

????? 如果你搜索網(wǎng)上分析dcl為什么在java中失效的原因,都會談到編譯器會做優(yōu)化云云,我相信大家看到這個一定會覺得很沮喪、很無助,對自己寫的 程序很沒信心。我很理解這種感受,因為我也經(jīng)歷過,這或許是為什么網(wǎng)上一直有人喜歡談dcl的原因。如果放在java5之前,從編譯器的角度去解釋dcl 也無可厚非,在java5的JMM(內(nèi)存模型)已經(jīng)得到很大的修正,如果到現(xiàn)在還只能從編譯器的角度去解釋dcl,那簡直就在污辱java,要知道 java的最大優(yōu)勢就是只需要考慮一個平臺。你可以完全無視網(wǎng)上絕大多數(shù)關(guān)于dcl的討論,很多時候他們自己都說不清楚,除Doug Lea等幾個大牛,我不相信誰比誰更權(quán)威。

很多人不理解dcl,不是dcl有多么復(fù)雜,恰恰相反,而是對基礎(chǔ)掌握得不夠。所以,我會先從基礎(chǔ)講起,然后再分析DCL。

我們都知道,當(dāng)兩個線程同時讀寫(或同時寫)一個共享變量時會發(fā)生數(shù)據(jù)競爭。那我怎么才能知道發(fā)生了數(shù)據(jù)競爭呢?我需要去讀取那個變量,發(fā)生數(shù)據(jù) 競爭通常有兩個表現(xiàn):一是讀取到陳舊數(shù)據(jù),即讀取到雖是曾經(jīng)寫入的數(shù)據(jù),但不是最新的。二是讀取到之前根本沒有寫入的值,也就是說讀到垃圾。

數(shù)據(jù)陳舊性

為了讀取到另一個線程寫入的最新數(shù)據(jù),JMM定義了一系列的規(guī)則,最基本的規(guī)則就是要利用同步。在Java中,同步的手段有synchronized和volatile兩種,這里我只會涉及到syncrhonized。請大家先記住以下規(guī)則,接下來我會細(xì)講。

規(guī)則一:必須對變量的所有寫和所有讀同步,才能讀取到該最新的數(shù)據(jù)。

先看下面的代碼:

Java代碼 ? 收藏代碼
  1. public ? class ?A?{??
  2. ???? private ? int ?some;??
  3. ???? public ? int ?another;??
  4. ??
  5. ???? public ? int ?getSome()?{? return ?some;?}??
  6. ???? public ? synchronized ? int ?getSomeWithSync()?{? return ?some;?}??
  7. ???? public ? void ?setSome( int ?v)?{?some?=?v;?}??
  8. ???? public ? synchronized ? void ?setSomeWithSync( int ?v)?{?some?=?v;?}??
  9. }??



讓我們來分析一個線程寫,另一個線程讀的情形,一共四種情形。初始情況都是a = new A(),暫不考慮其它線程。

情形一:讀寫都不同步。

Thread1 Thread2
(1) a.setSome(13)
(2) a.getSome()



Java線程安全兼談DCL

這種情況下,即使thread1先寫入some為13,thread2再讀取some,它能讀到13嗎?在沒有同步協(xié)調(diào)下,結(jié)果是不確定的。從圖 上看出,兩個線程獨立運行,JMM并不保證一個線程能夠看到另一個線程寫入的值。在這個例子中,就是thread2可能讀到0(即some的初始值)而不 是13。注意,在理論上,即使thread2在thread1寫入some之后再等上一萬年也還是可能讀到some的初始值0,盡管這在實際幾乎不可能發(fā) 生。


情形二:寫同步,讀不同步

Thread1 Thread2
(1) a.setSomeWithSync(13)
(2) a.getSome()



Java線程安全兼談DCL

情形三:讀同步,寫不同步

Thread1 Thread2
(1) a.setSome(13)
(2) a.getSomeWithSync()



Java線程安全兼談DCL

在這兩種情況下,thread1和thread2只對讀或只對寫some加了鎖,這不起任何作用,和[情形一]一樣,thread2仍有可能讀到 some的初始值0。從圖上也可看出,thread1和thread2互相之間并沒有任何影響,一方加鎖并不影響另一方的繼續(xù)運行。圖中也顯示,同步操作 相當(dāng)于在同步開始執(zhí)行l(wèi)ock操作,在同步結(jié)束時執(zhí)行unlock操作。

情形四:讀寫都同步

Thread1 Thread2
(1) a.setSomeWithSync(13)
(2) a.getSomeWithSync()



Java線程安全兼談DCL

在情形四中,thread1寫入some時,thread2等待thread1寫入完成,并且它能看到thread1對some做的修改,這時 thread2保證能讀到13。實際上,thread2不僅能看到thread1對some的修改,而且還能看到thread1在修改some之前所做的 任何修改。說得更精確一些,就是一個線程的lock操作能看見另一線程對同一個對象unlock操作之前的所有修改,請注意圖中的紅色箭頭。 沿著圖中箭頭指示方向,箭頭結(jié)尾處總能看到箭頭開始處操作做的修改。這樣,a.some[thread2]能看見 lock[thread2],lock[thread2]能看見unlock[thread1],unlock[thread1]又能看見 a.some=13[thread1],即能看到some的值為13。

再來看一個稍微復(fù)雜一點的例子:

例子五

Thread1 Thread2
(1) a.another = 5
(2) a.setSomeWithSync(13)
(3) a.getSomeWithSync()
(4) a.another = 7
(5) a.another



Java線程安全兼談DCL

thread2最后會讀到another的什么值呢?會不會讀到another的初始值0呢,畢竟所有對another的訪問都沒有同步?不會。 從圖中很清晰地可以看出,thread2的another至少到看到thread1在lock之前寫入的5,卻并不能保證它能看到thread1在 unlock寫入的7。因此,thread2可以什么讀到another的值可能5或7,但不會是0。你或許已經(jīng)發(fā)現(xiàn),如果去掉圖中thread2讀取 a.some的操作,這時相當(dāng)于一個空的同步塊,對結(jié)論并沒有任何影響。這說明空的同步塊是起作用的,編譯器不能擅自將空的同步塊優(yōu)化掉,但你在使用空的 同步塊應(yīng)該特別小心,通常它都不是你想要的結(jié)果。另外需要注意,unlock操作和lock操作必須針對同一個對象,才能保證unlock操作能看到 lock操作之前所做的修改。

例子六:不同的鎖

Java代碼 ? 收藏代碼
  1. class ?B?{??
  2. ???? private ?Object?lock1?=? new ?Object();??
  3. ???? private ?Object?lock2?=? new ?Object();??
  4. ??
  5. ???? private ? int ?some;??
  6. ??
  7. ???? public ? int ?getSome()?{??
  8. ???????? synchronized (lock1)?{? return ?some;?}??
  9. ????}??
  10. ??
  11. ???? public ? void ?setSome( int ?v)?{??
  12. ???????? synchronized (lock2)?{?some?=?v;?}??
  13. ????}??
  14. }??

?

Thread1 Thread2
(1) b.setSome(13)
(2) b.getSome()



Java線程安全兼談DCL

在這種情況下,雖然getSome和setSome都加了鎖,但由于它們是不同的鎖,一個線程運行時并不能阻塞另一個線程運行。因此這里的情形和情形一、二、三一樣,thread2不保證讀到thread1寫入的some最新值。

現(xiàn)在來看DCL:

例子七: DCL

Java代碼 ? 收藏代碼
  1. public ? class ?LazySingleton?{??
  2. ???? private ? int ?someField;??
  3. ??????
  4. ???? private ? static ?LazySingleton?instance;??
  5. ??????
  6. ???? private ?LazySingleton()?{??
  7. ???????? this .someField?=? 201 ;????????????????????????????????? //?(1) ??
  8. ????}??
  9. ??????
  10. ???? public ? static ?LazySingleton?getInstance()?{??
  11. ???????? if ?(instance?==? null )?{??????????????????????????????? //?(2) ??
  12. ???????????? synchronized (LazySingleton. class )?{??????????????? //?(3) ??
  13. ???????????????? if ?(instance?==? null )?{??????????????????????? //?(4) ??
  14. ????????????????????instance?=? new ?LazySingleton();??????????? //?(5) ??
  15. ????????????????}??
  16. ????????????}??
  17. ????????}??
  18. ???????? return ?instance;?????????????????????????????????????? //?(6) ??
  19. ????}??
  20. ??????
  21. ???? public ? int ?getSomeField()?{??
  22. ???????? return ? this .someField;???????????????????????????????? //?(7) ??
  23. ????}??
  24. }??



假設(shè)thread1先調(diào)用getInstance(),由于此時還沒有任何線程創(chuàng)建LazySingleton實例,它會創(chuàng)建一個實例s并返回。 這是thread2再調(diào)用getInstance(),當(dāng)它運行到(2)處,由于這時讀instance沒有同步,它有可能讀到s或者null(參考情形 二)。先考慮它讀到s的情形,畫出流程圖就是下面這樣的:

Java線程安全兼談DCL

由于thread2已經(jīng)讀到s,所以getInstance()會立即返回s,這是沒有任何問題,但當(dāng)它讀取s.someFiled時問題就發(fā)生 了。 從圖中可以看thread2沒有任何同步,所以它可能看不到thread1寫入someField的值20,對thread2來說,它可能讀到 s.someField為0,這就是DCL的根本問題。從上面的分析也可以看出,為什么試圖修正DCL但又希望完全避免同步的方法幾乎總是行不通的。

接下來考慮thread2在(2)處讀到instance為null的情形,畫出流程圖:

Java線程安全兼談DCL

接下來thread2會在有鎖的情況下讀取instance的值,這時它保證能讀到s,理由參考情形四或者通過圖中箭頭指示方向來判定。

關(guān)于DCL就說這么多,留下兩個問題:

  1. 接著考慮thread2在(2)讀到instance為null的情形,它接著調(diào)用s.someFiled會得到什么?會得到0嗎?
  2. DCL為什么要double check,能不能去掉(4)處的check?若不能,為什么?



原子性
回到情形一,為什么我們說thread2讀到some的值只可能為為0或13,而不可能為其它?這是由java對int、引用讀寫都是原子性所決 定的。所謂“原子性”,就是不可分割的最小單元,有數(shù)據(jù)庫事務(wù)概念的同學(xué)們應(yīng)該對此容易理解。當(dāng)調(diào)用some=13時,要么就寫入成功要么就寫入失敗,不 可能寫入一半。但是,java對double, long的讀寫卻不是原子操作,這意味著可能發(fā)生某些極端意外的情況。看例子:

Java代碼 ? 收藏代碼
  1. public ? class ?C?{??
  2. ???? private ? /*?volatile?*/ ? long ?x;??????????????????????????? //?(1) ??
  3. ??
  4. ???? public ? void ?setX( long ?v)?{?x?=?v;?}??
  5. ???? public ? long ?getX()?{? return ?x;?}??
  6. }??

?

Thread1 Thread2
(1) c.setX(0x1122334400112233L)
(2) c.getX()



thread2讀取x的值可能為0,1122334400112233外,還可能為別的完全意想不到的值。一種情況假設(shè)jvm對long的寫入是 先寫低4字節(jié),再寫高4字節(jié),那么讀取到x的值還可能為112233。但是我們不對jvm做如此假設(shè),為了保證對long或double的讀寫是原子操 作,有兩種方式,一是使用volatile,二是使用synchronized。對上面的例子,如果取消(1)處的volatile注釋,將能保證 thread2讀取到x的值要么為0,要么為1122334400112233。如果使用同步,則必須像下面這樣對getX,setX都同步:

Java代碼 ? 收藏代碼
  1. public ? class ?C?{??
  2. ???? private ? /*?volatile?*/ ? long ?x;??????????????????????????? //?(1) ??
  3. ??
  4. ???? public ? synchronized ? void ?setX( long ?v)?{?x?=?v;?}??
  5. ???? public ? synchronized ? long ?getX()?{? return ?x;?}??
  6. }??



因此對原子性也有規(guī)則(volatile其實也是一種同步)。

規(guī)則二:對double, long變量,只有對所有讀寫都同步,才能保證它的原子性

有時候我們需要保證一個復(fù)合操作的原子性,這時就只能使用synchronized。

Java代碼 ? 收藏代碼
  1. public ? class ?Canvas?{??
  2. ???? private ? int ?curX,?curY;??
  3. ??
  4. ???? public ? /*?synchronized?*/ ?getPos()?{??
  5. ???????? return ? new ? int []?{?curX,?curY?};??
  6. ??????????
  7. ????}??
  8. ??
  9. ???? public ? /*?synchronized?*/ ? void ?moveTo( int ?x,? int ?y)?{??
  10. ????????curX?=?x;??
  11. ????????curY?=?y;??
  12. ????}??
  13. }??

?

Thread1 Thread2
(1) c.moveTo(1, 1)
(2) c.moveTo(2, 2)
(3) c.getPos()



當(dāng)沒有同步的情況下,thread2的getPos可能會得到[1, 2], 盡管該點可能從來沒有出現(xiàn)過。之所以會出現(xiàn)這樣的結(jié)果,是因為thread2在調(diào)用getPos()時,curX有0,1或2三種可能,同樣curY也有 0,1或2三種可能,所以getPos()可能返回[0,0], [0,1], [0,2], [1,0], [1,1], [1,2], [2,0], [2,1], [2,2]共九種可能。要避免這種情況,只有將getPos()和moveTo都設(shè)為同步方法。

總結(jié)
以上分析了數(shù)據(jù)競爭的兩種癥狀,陳舊數(shù)據(jù)和非原子操作,都是由于沒有恰當(dāng)同步引起的。這些其實都是相當(dāng)基礎(chǔ)的知識,同步可有兩種效果:一是保證讀 取最新數(shù)據(jù),二是保證操作原子性,但是大多數(shù)書籍都對后者過份強調(diào),對前者認(rèn)識不足,以致對多線程的認(rèn)識上存在很多誤區(qū)。如果想要掌握java線程高級知 識,我只推薦《Java并發(fā)編程設(shè)計原則與模式》。其實我已經(jīng)好久沒有寫Java了,這些東西都是我兩年前的知識,如果存在問題,歡迎大家指出,千萬不要 客氣。

Java線程安全兼談DCL


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 日韩亚洲综合精品国产 | 久久久久久久男人的天堂 | 欧美色图片区 | 色天天干| 国产区综合 | 中文字幕亚洲在线 | 成人精品视频 成人影院 | 久久草在线视频免费 | 欧美成人区 | 久久国产在线观看 | 美国毛片aaa在线播放 | 日日私人影院 | 国产福利99 | 精品一区二区三区亚洲 | 九九99久麻豆精品视传媒 | 欧美久久xxxxxx影院 | 欧美综合图片区 | 国产免费不卡v片在线观看 国产免费不卡视频 | 欧美国产日韩在线观看 | 日本三级日本三级人妇三级四 | 一本色道久久综合狠狠躁 | 天天搞天天操 | 国产精品第1页在线播放 | 国产性一交一乱一伦一色一情 | 亚洲日本一区二区三区高清在线 | 性ao爱大片 | 久久久久久夜精品精品免费 | 一本久道久久综合中文字幕 | 欧美一级毛片一级 | 欧美成人精品欧美一级乱黄 | 国国产自国偷自产第38页 | 国产精品亚洲综合 | 欧美精品h在线播放 | 日本在线不卡视频 | 欧美成人毛片一级在线 | 精品国产91乱码一区二区三区 | 正在播放久久 | 自拍 亚洲 欧美 | 日本a在线 | 免费爱爱的视频太爽了 | 日本久久精品免视看国产成人 |