java 双重检查加锁弊端
http://blog.csdn.net/axman/article/details/1089196
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;
 ????}
 }
 那么如何保證實例字段能在工作存儲區能被即時映,下一節我們來講最不常用的關鍵字
?
對于涉及對象初始化的DCL,從JAVA1.5以后雖然可以用volatile來修補,但已經沒有任何意義。因為
?
比它更好的模式lazy initialization hoder可以達到相同的作用而更容易理解:
?
?
?
public MyObject{
?
?
?
??? private static class instanceHoder{//內部私有的類,我特別用了小寫開頭。
?
??????? 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是類的定義并不會引起初始化。只有在首次調用getInstance時才會加載
?
instanceHoder類然后初始化instance實例。而靜態初始化是由JVM來保證線程安全的,所以整個過程都不需要同
?
步參與,極大地提高了性能。
轉載于:https://my.oschina.net/vshcxl/blog/876852
總結
以上是生活随笔為你收集整理的java 双重检查加锁弊端的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: sqlserver 人名_sqlserv
- 下一篇: mysql的含义及特点_MySQL——基
