風(fēng)人園

          弱水三千,只取一瓢,便能解渴;佛法無邊,奉行一法,便能得益。
          隨筆 - 99, 文章 - 181, 評(píng)論 - 56, 引用 - 0
          數(shù)據(jù)加載中……

          多線程編程的設(shè)計(jì)模式 臨界區(qū)模式

          多線程編程的設(shè)計(jì)模式 臨界區(qū)模式(一)

          臨界區(qū)模式 Critical Section Pattern 是指在一個(gè)共享范圍中只讓一個(gè)線程執(zhí)行的模式.
          它是所有其它多線程設(shè)計(jì)模式的基礎(chǔ),所以我首先來介紹它.
          把著眼點(diǎn)放在范圍上,這個(gè)模式叫臨界區(qū)模式,如果把作眼點(diǎn)放在執(zhí)行的線程上,這個(gè)模式就叫
          單線程執(zhí)行模式.

          首先我們來玩一個(gè)鉆山洞的游戲,我 Axman,朋友 Sager,同事 Pentium4.三個(gè)人在八角游樂場
          循環(huán)鉆山洞(KAO,減肥訓(xùn)練啊),每個(gè)人手里有一個(gè)牌子,每鉆一次洞口的老頭會(huì)把當(dāng)前的次序,
          姓名,牌號(hào)顯示出來,并檢查名字與牌號(hào)是否一致.

          OK,這個(gè)游戲的參與者有游樂場老頭Geezer,Player,就是我們,還有山洞 corrie.

          public class Geezer {
          ??? public static void main(String[] args){
          ???????
          ??????? System.out.println("預(yù)備,開始!");
          ??????? Corrie c = new Corrie();//只有一個(gè)山洞,所以生存一個(gè)實(shí)例后傳給多個(gè)Player.
          ??????? new Player("Axman","001",c).start();
          ??????? new Player("Sager","002",c).start();
          ??????? new Player("Pentium4","003",c).start();
          ??? }
          }

          這個(gè)類暫時(shí)沒有什么多說的,它是一個(gè)Main的角色.

          public class Player extends Thread{
          ??? private final String name;
          ??? private final String number;
          ??? private final Corrie corrie;
          ??? public Player(String name,String number,Corrie corrie) {
          ??????? this.name = name;
          ??????? this.number = number;
          ??????? this.corrie = corrie;
          ??? }
          ???
          ??? public void run(){
          ??????? while(true){
          ??????????? this.corrie.into(this.name,this.number);
          ??????? }
          ??? }
          }
          在這里,我們把成員字段都設(shè)成final的,為了說明一個(gè)Player一旦構(gòu)造,他的名字和牌號(hào)就不能改
          變,簡單說在游戲中,我,Sager,Pentium4三個(gè)人不會(huì)自己偷偷把自己的牌號(hào)換了,也不會(huì)偷偷地去
          鉆別的山洞,如果這個(gè)游戲一旦發(fā)生錯(cuò)誤,那么錯(cuò)誤不在我們玩家.

          import java.util.*;
          public class Corrie {
          ??? private int count = 0;
          ??? private String name;
          ??? private String number;
          ??? private HashMap lib = new HashMap();//保存姓名與牌號(hào)的庫
          ???
          ??? public Corrie(){
          ???????
          ??????? lib.put("Axman","001");
          ??????? lib.put("Sager","002");
          ??????? lib.put("Pentium4","003");
          ?
          ??? }
          ???
          ??? public void into(String name,String number){
          ??????? this.count ++;
          ??????? this.name = name;
          ??????? this.number = number;
          ??????? if(this.lib.get(name).equals(number))
          ?test():
          ??? }
          ???
          ??? public String display(){
          ??????? return this.count+": " + this.name + "(" + this.number + ")";
          ??? }

          ??? private void test(){
          ??????? if(this.lib.get(name).equals(number))
          ??????????? ;
          ??????????? //System.out.println("OK:" + display());
          ??????? else
          ??????????? System.out.println("ERR:" + display());
          ??? }
          }
          這個(gè)類中增加了一個(gè)lib的HashMap,相當(dāng)于一個(gè)玩家姓名與牌號(hào)的庫,因?yàn)槊髦繡orrie只有一個(gè)實(shí)例,
          所以我用了成員對(duì)象而不是靜態(tài)實(shí)例,只是為了能在構(gòu)造方法中初始化庫中的內(nèi)容,從真正意義中說應(yīng)
          該在一個(gè)輔助類中實(shí)現(xiàn)這樣的數(shù)據(jù)結(jié)構(gòu)封裝的功能.如果不提供這個(gè)lib,那么在check的時(shí)候就要用
          if(name.equasl("Axman")){
          ?if(!number.equals("001")) //出錯(cuò)
          }
          else if .......
          這樣復(fù)雜的語句,如果player大多可能會(huì)寫到手抽筋,所以用一個(gè)lib來chcek就非常容象.


          運(yùn)行這個(gè)程序需要有一些耐心,因?yàn)榧词鼓愕某绦驅(qū)懙迷俨钤诤芏鄦尉€程測試環(huán)境下也能可是正確的.
          而且多線程程序在不同的機(jī)器上表現(xiàn)不同,要發(fā)現(xiàn)這個(gè)例子的錯(cuò)識(shí),可能要運(yùn)行很長一段時(shí)間,如果你的
          機(jī)器是多CPU的,那么出現(xiàn)錯(cuò)誤的機(jī)會(huì)就大好多.

          在我的筆記本上最終出現(xiàn)錯(cuò)誤是在11分鐘以后,出現(xiàn)的錯(cuò)誤有幾鐘情況:
          1: ERR:Axman(003)
          2: ERR:Sager(002)
          第一種情況是檢查到了錯(cuò)誤,我的牌號(hào)明明是001,卻打印出來003,而第二種明明沒有錯(cuò)誤,卻打印了錯(cuò)誤.

          事實(shí)上根據(jù)以前介紹的多線程知識(shí),不難理解這個(gè)例子的錯(cuò)誤出現(xiàn),因?yàn)閕nto不是線程安全的,所以在其中
          一個(gè)線程執(zhí)行this.name = "Axman";后,本來應(yīng)該執(zhí)行this.numner="001",卻被切換到另一個(gè)線程中執(zhí)行
          this.number="003",然后又經(jīng)過不可預(yù)知的切換執(zhí)行其中一個(gè)的if(this.lib.get(name).equals(number))
          而出現(xiàn)1的錯(cuò)誤,而在打印這個(gè)錯(cuò)誤時(shí)因?yàn)閐isplay也不是線程安全的,正要打印一個(gè)錯(cuò)誤的結(jié)果時(shí),由于
          this.name或this.number其中一個(gè)字段被修改卻成了正確的匹配而出現(xiàn)錯(cuò)誤2.

          另外還有可能會(huì)出現(xiàn)序號(hào)顛倒或不對(duì)應(yīng),但這個(gè)錯(cuò)誤我們無法直觀地觀察,因?yàn)槟愀静恢滥膫€(gè)序號(hào)"應(yīng)該"
          給哪個(gè)Player,而序號(hào)顛倒則有可能被滾動(dòng)的屏幕所掩蓋.


          [正確的Critical Section模式的例子]
          我們知道出現(xiàn)這些錯(cuò)誤是因?yàn)镃orrie類的方法不是線程安全的,那么只要修改Corrie類為線程安全的類就行
          了.其它類則不需要修改,上面說過,如果出現(xiàn)錯(cuò)誤那一定不是我們玩家的事:

          ?

          import java.util.*;
          public class Corrie {
          ??? private int count = 0;
          ??? private String name;
          ??? private String number;
          ??? private HashMap lib = new HashMap();//保存姓名與牌號(hào)的庫
          ???
          ??? public Corrie(){
          ???????
          ??????? lib.put("Axman","001");
          ??????? lib.put("Sager","002");
          ??????? lib.put("Pentium4","003");
          ?
          ??? }
          ???
          ??? public synchronized void into(String name,String number){
          ??????? this.count ++;
          ??????? this.name = name;
          ??????? this.number = number;
          ?test();
          ??? }
          ???
          ??? public synchronized String display(){
          ??????? return this.count+": " + this.name + "(" + this.number + ")";
          ??? }

          ??? private void test(){
          ??????? if(this.lib.get(name).equals(number))
          ??????????? ;
          ??????????? //System.out.println("OK:" + display());
          ??????? else
          ??????????? System.out.println("ERR:" + display());
          ??? }
          }

          運(yùn)行這個(gè)例子,如果你的耐心,開著你的機(jī)器運(yùn)行三天吧.雖然測試100天并不能說明第101天沒有出錯(cuò),
          at least,現(xiàn)在的正確性比原來那個(gè)沒有synchronized 保護(hù)的例子要可靠多了!

          到這里我們對(duì)Critical Section模式的例程有了直觀的了解,在詳細(xì)解說這個(gè)模式之前,請(qǐng)想一下,test
          方法安全嗎?為什么?

          所謂模式就是脫離特定的例子使用更一般化的,通用化的表達(dá)方式來察看,描述,總結(jié)相同的問題.現(xiàn)在
          我們來研究這個(gè)模式:

          共享資源(sharedResource)參與者:
          在臨界區(qū)模式中,一定有一個(gè)或一個(gè)以上的共享資源角色的參與.在上面這個(gè)例子中就是山洞(Corrie).

          共享資源參與者會(huì)被多個(gè)線程訪問,這個(gè)角色的訪問方法有兩種類型,一種是多個(gè)線程訪問也不會(huì)發(fā)生問
          題的方法,稱為線程安全的方法,另一種就是在多個(gè)線程同時(shí)訪問時(shí)會(huì)發(fā)生問題需要保護(hù)的方法,稱為不安
          全的方法.


          這里所說的線程安全和不安全的方法,不用多說大家都知道是指公開的方法.對(duì)上節(jié)最后我留下的問題而
          言,test方法是安全的,因?yàn)樗莗rivate的,只會(huì)被into方法調(diào)用,而into方法是同步的,簡單說test中的
          代碼一定會(huì)在同步塊中執(zhí)行,而display方法是public的,有可能被任何線程調(diào)用,所以它需要同步.

          對(duì)于線程安全的方法,不需要多說.而對(duì)于不安全的方法,只要定義為synchronized的就可以達(dá)到保護(hù)的
          目的.也就是多個(gè)線程同時(shí)執(zhí)行該段代碼時(shí),只有一個(gè)線程有機(jī)會(huì)執(zhí)行,具體機(jī)制我們?cè)诙嗑€程中同步對(duì)象
          鎖中已經(jīng)說明過.我們把這種只有一個(gè)線程能進(jìn)入的程序范圍,稱為[臨界區(qū)]


          盡管JDK5以后提供了很多功能更強(qiáng),語義更準(zhǔn)確的并發(fā)控制的接口供程序員調(diào)用,但我還是極力推薦在大
          多數(shù)情況下(除非需要有效的控制)還是使用synchronized來保護(hù)臨界區(qū),因?yàn)閟ynchronized塊的開始和結(jié)
          束是自動(dòng)控制的,在離開同步塊時(shí)會(huì)自動(dòng)釋放同步對(duì)象鎖.而使用java的lock對(duì)象時(shí),你不得不每時(shí)每刻小
          心地在finally從句中調(diào)用lock對(duì)象的unlock方法,這比在finally從句中釋放數(shù)據(jù)庫連結(jié)更重要!

          [適用環(huán)境]

          1.單線程環(huán)境:單線程環(huán)境中肯定只有一個(gè)線程執(zhí)行,無論是否在臨界區(qū)中反正只有一個(gè)線程執(zhí)行,所以沒
          有必要用synchronized保護(hù),當(dāng)然如果你非想用synchronized保護(hù)沒有問題,只是會(huì)引起性能的降低,但不
          會(huì)降低太大.這就象一個(gè)人在家里已經(jīng)關(guān)上了大門,還關(guān)著臥室的小門,除了會(huì)給你帶來一些不便之處,沒有
          什么太大的損失.

          2.多線程環(huán)境:如果這些多線程環(huán)境中各自完全獨(dú)立地運(yùn)行,當(dāng)然沒有問題.但如果多個(gè)線程可能訪問同一
          SharedResource對(duì)象時(shí),就需要使用臨界區(qū)模式來保護(hù).有時(shí)管理線程的環(huán)境會(huì)提供一種SafeThread環(huán)境來
          確保線程的獨(dú)立,這種情況就不需要使用臨界區(qū)模式.

          3.SharedResource的狀態(tài)會(huì)發(fā)生改變的情況才需要使用這個(gè)模式,如果SharedResource對(duì)象一經(jīng)生成就不
          會(huì)改變,當(dāng)然不需要保護(hù).(只讀模式)

          4.在必要的確保安全性的時(shí)候使用這個(gè)模式.比如java數(shù)據(jù)結(jié)構(gòu)類大多數(shù)都不是線程安全的.因?yàn)楹芏嗲闆r
          下發(fā)生多個(gè)線程共享沖突對(duì)程序本身并無大礙,比如用一個(gè)ArrayList或HashMap存放在線人數(shù),對(duì)于在線
          人數(shù)這種數(shù)據(jù)本來就不可能精確地計(jì)算,只是相對(duì)時(shí)間內(nèi)的一個(gè)概數(shù),所以多個(gè)線程訪問對(duì)產(chǎn)生沖突對(duì)其幾
          乎沒有影響.
          但是對(duì)于需要確保線程安全的時(shí)候,java仍然提供了大量的線程安全的數(shù)據(jù)結(jié)構(gòu)的封裝,由Collections類
          提供的synchronizedXXX()方法可以將傳入的數(shù)據(jù)結(jié)構(gòu)封裝為線程安全的.


          [性能因素]
          在程序設(shè)計(jì)中,大多數(shù)情況下,各種優(yōu)點(diǎn)無法共存,事實(shí)上如果使用一個(gè)模式能給其它方面的優(yōu)點(diǎn)也帶來提
          升那簡單就沒有理由不使用該模式了.對(duì)于安全性的提升往往要以犧牲性能為代價(jià),所以臨界區(qū)模式會(huì)帶來
          一些性能方面的損失.如何權(quán)衡這它們之間的比例,要看程序運(yùn)行的環(huán)境,目的等各方面的因素.

          1.獲取對(duì)象鎖的操作本身是要花時(shí)間的.一個(gè)線程在獲取同步對(duì)象鎖時(shí),其實(shí)就是一個(gè)全局對(duì)象的自旋鎖,這
          個(gè)全局對(duì)象是要注冊(cè)到線程管理系統(tǒng)中的.這個(gè)過程本身需要一定的時(shí)間.但這個(gè)過程性能影響并不大.

          2.同步對(duì)象鎖被其它線程占用時(shí)需要等待.當(dāng)一個(gè)線程進(jìn)入同步塊時(shí),獲取該同步對(duì)象的鎖,如果該鎖被其它
          線程擁有測當(dāng)前線程必須等待,從而降低性能,這方面性能的降低較大.

          提高性能的方法一是盡量減少共享資源的數(shù)量.二是盡量減小臨界區(qū)的范圍.雙檢鎖模式就是減小臨界區(qū)范

          圍的一種手段.



          [死鎖問題]
          臨界區(qū)模式中非常重要的一點(diǎn)是多線程程序的生命指數(shù).再安全的程序如果運(yùn)行一定時(shí)間就結(jié)束自己的生命
          而不能繼續(xù)運(yùn)行,那就根本不能達(dá)到設(shè)計(jì)的目的.除去系統(tǒng)突發(fā)因素,影響生命指數(shù)的最大原因就是死鎖.
          對(duì)于大家都熟悉的五個(gè)哲學(xué)家(好象是故意調(diào)侃哲學(xué)家)吃面條的例子,我們用最簡單的模型簡單為兩個(gè)哲學(xué)
          家.然后從中抽象出死鎖的最一般的條件:

          1.有多個(gè)共享資源被多線程共享.對(duì)于兩個(gè)吃面的哲學(xué)家而言就是刀和叉兩上以上的共享資源.

          2.對(duì)一個(gè)共享資源的占用還沒有釋放鎖又獲取另一個(gè)共享資源.占用了刀的時(shí)候又要獲取叉.

          3.對(duì)共享資源的占用順序是不固定的.如果哲學(xué)家按一定順序使用刀和叉,一個(gè)用完了思考時(shí)再讓給另一個(gè)
          用那就能很好地完成目標(biāo)而不會(huì)發(fā)生死鎖,正時(shí)因?yàn)閷?duì)共享資源占用的順序是無法確定的.當(dāng)一個(gè)結(jié)程占用
          一個(gè)共享資源時(shí),要獲取另一個(gè)線程占用的共享資源,而另一個(gè)線程釋放這個(gè)共享資源的條件是以獲取被原
          先被占用的共享資源時(shí),才會(huì)發(fā)生死鎖.

          所以如果我們破壞上面其中之一的條件就不會(huì)發(fā)生死鎖問題,也就是在設(shè)計(jì)時(shí)要考慮不要同時(shí)發(fā)生上面的
          三程情況.

          [嵌套鎖定]
          對(duì)于同一對(duì)象的嵌套鎖定,例子如下:
          synchronized(this){//1
          ????System.out.println("outter");
          ????synchronized(this){//2
          ????????System.out.println("inner");
          ????}
          }
          這個(gè)例子能運(yùn)行嗎?答案是可以很好地運(yùn)行.
          一般以為線程運(yùn)行到1時(shí),獲取了當(dāng)前對(duì)象鎖,打印outter后,運(yùn)行到2,又要獲取當(dāng)前對(duì)象鎖,而此時(shí)當(dāng)前對(duì)象
          鎖還沒有釋放,所以線程一直等在這兒發(fā)生死鎖.
          其實(shí)java是一種smart?language,在編譯的時(shí)候,它就會(huì)檢查對(duì)同一對(duì)象的嵌套鎖定.因?yàn)椴豢赡馨l(fā)生在層同
          步塊中有多個(gè)線程進(jìn)入而其中一個(gè)線程要進(jìn)入內(nèi)層同步塊的情況,也就是外層同步塊本身就可以保證只有一
          個(gè)線程獲取同步對(duì)象的鎖,所以內(nèi)層同一對(duì)象的同步塊在編譯的時(shí)候已經(jīng)失去它的作用.

          [繼承和擴(kuò)展]
          對(duì)于臨界區(qū)模式而言,即使我們已經(jīng)使用synchronized方法對(duì)共享資源進(jìn)行保護(hù),但是子類在擴(kuò)展接口時(shí)很可
          能將共享資源以不安全方式暴露出去.這是非常值得注意的問題.設(shè)計(jì)時(shí)應(yīng)該盡時(shí)將對(duì)共享資源的訪問方法加
          以保護(hù),可以使用private和final等限制,另外在子類設(shè)計(jì)時(shí)也要充分考慮對(duì)父類共享資源的訪問.

          [高級(jí)主題:關(guān)于synchronized]

          其實(shí)在多線程編程基礎(chǔ)部分,我已經(jīng)談過synchronized相關(guān)的內(nèi)容.但臨界區(qū)模式是其它多線程編程模式的基
          礎(chǔ),所以在這里繼續(xù)深入一下談?wù)剆ynchronized相關(guān)的一些內(nèi)容.

          只要見到synchronized關(guān)鍵字,第一要想到的問題就是,synchronized在保護(hù)誰?

          在上面的例子中,synchronized保護(hù)的是Corrie對(duì)象的counter,name,number三個(gè)字段不被"交差賦值",也就是
          這三個(gè)字段同時(shí)只能被一個(gè)線程訪問.
          其次我們要考慮的問題是:這些對(duì)象都被妥善地保護(hù)了嗎?

          這是非常重要的問題.無論你花巨資打造一把高安全性鎖,把自己的家門牢牢地鎖住,可是你卻把門旁邊的窗子
          敞開著,那么你花巨資打造的鎖又要什么意義呢?所以要確保從任何一個(gè)通道訪問被保護(hù)的對(duì)象都被加鎖控制
          的,比如字段是否都private或protected的,對(duì)于protected的子類中的擴(kuò)展方法是否能保護(hù)被保護(hù)對(duì)象.

          對(duì)于上面的例子因?yàn)閐isplay有可能被外面的方法單獨(dú)調(diào)用,所以它也必須是同步的.而test方法只會(huì)在into中
          調(diào)用,簡單說它只是所有通道被加了鎖的大房子中的一個(gè)小單元,所以不必?fù)?dān)心有人會(huì)從外部訪問它.

          要注意保護(hù)的范圍是三個(gè)同時(shí)需要保護(hù)的字段,如果它們被分別放在synchronized方法中保護(hù),并不能保證它們
          本個(gè)字段同時(shí)只有一個(gè)線程訪問.

          那么我們就有一個(gè)問題,獲取誰的鎖呢?

          要保護(hù)一個(gè)對(duì)象,當(dāng)然直接獲取這個(gè)對(duì)象的鎖,我們上面的例子可以理解為要同時(shí)保護(hù)三個(gè)對(duì)象,那么其實(shí)就是
          要保護(hù)這個(gè)本個(gè)對(duì)象的容器.也就是它們所在的實(shí)例.如果不相關(guān)的三個(gè)對(duì)象要同時(shí)保護(hù),一定要放在同時(shí)容納
          它們的容器中,否則無法同時(shí)保護(hù)它們的狀態(tài).對(duì)于上面的例子我們同樣可以理解為要保護(hù)的是Corrie的實(shí)例,
          因?yàn)檫@個(gè)實(shí)例是這三個(gè)字段的容器.所以我們用synchronized方法就是等同于synchronized(this){.......}
          如果這個(gè)游戲中有多個(gè)山洞,而只有一塊顯示牌,那以我們就需要保護(hù)多個(gè)實(shí)例的三個(gè)字段同時(shí)只被一個(gè)線程
          訪問,我們就需要synchronized(Corrie.class)來保證多個(gè)實(shí)例被多個(gè)線程訪問時(shí)只有一個(gè)對(duì)程能同時(shí)對(duì)三個(gè)
          字段訪問.
          所以獲取誰的鎖定也是一個(gè)很重要的問題,如果你選擇了錯(cuò)誤的對(duì)象,就象你花巨資打了一把鎖卻鎖了別人的
          門.

          synchronized就是原子操作,簡單說在一個(gè)線程進(jìn)行同步塊中的代碼時(shí)不能進(jìn)入,這是很明顯的.但同時(shí),多個(gè)
          同步方法或多個(gè)獲取同一對(duì)象的同步塊在同一時(shí)候也只能一個(gè)線程能訪問其中之一,因?yàn)榭刂普l能訪問的是要
          獲得那個(gè)同步對(duì)象的鎖.如:
          class C{
          ?synchronized? a(){}
          ?synchronized? b(){}
          }

          當(dāng)一個(gè)線程進(jìn)入同步方法a后那么其它線程當(dāng)然不能進(jìn)入a,同時(shí)也不能進(jìn)入b,因?yàn)槟苓M(jìn)入的條件是獲取this對(duì)
          象的鎖.一個(gè)結(jié)程進(jìn)入a后this對(duì)象的鎖被這個(gè)線程獲取,其它線程進(jìn)入b也同樣要獲取這個(gè)鎖,而不僅僅是進(jìn)入
          a要獲取這個(gè)鎖.這一點(diǎn)一定要理解.

          理解上面的知識(shí)我們?cè)倩剡^頭來看原子操作.

          JLS規(guī)定對(duì)于基本類型(除long和double)以外的賦值和引用都是原子操作,并且對(duì)于引用類型的賦值和引用也是
          原子操作.

          注意這里有兩個(gè)方面的知識(shí)點(diǎn):

          1.對(duì)于long和double的操作非原子性的.需要說明這只是JLS的規(guī)定,但大多數(shù)JVM的實(shí)現(xiàn)其實(shí)已經(jīng)保證了long和
          double的賦值和引用也是原子性的,只是允許某種實(shí)現(xiàn)可以不是原子性的操作.

          對(duì)于其它基本類型如int,如果一個(gè)線程執(zhí)行x = 1;另一個(gè)線程執(zhí)行x = 2;
          由于可見性的問題(多線程編程系統(tǒng)中已經(jīng)介紹),x要么就是1,要么就是2,看誰先同步到主存儲(chǔ)區(qū).

          但對(duì)于long,l = 1;l = 2;分別由兩個(gè)線程執(zhí)行的結(jié)果有可能不是你想象的,它們有可能是0,或1,或2,或一個(gè)其
          它的隨機(jī)數(shù),簡單說兩上線程中l(wèi)的值的部分bit位可能被另一個(gè)線程改寫.所以最可靠的是放在synchronized中
          或用volatile 保護(hù).當(dāng)然這里說的是"有非常可靠的需要",一般而言現(xiàn)在的JVM已經(jīng)能保證long和double也是原
          子操作的.

          2.我們看到,對(duì)于引用對(duì)象的賦值和引用也是原子的.

          我們還是看javaworld上dcl的例子.

          ?那個(gè)錯(cuò)誤的例子誤了好多人,(JAVA與模式的作者就是受害人),我們先不說JAVA內(nèi)存模型的原因(前面我已經(jīng)從
          JAVA內(nèi)存模型上說明了那個(gè)例子是錯(cuò)誤的,我是說對(duì)那個(gè)例子的分析是錯(cuò)誤的).單從對(duì)于"引用對(duì)象的賦值和引
          用也是原子的"這句話,就知道對(duì)于引用字段的賦值,絕對(duì)不可能出現(xiàn)先分配空間,然后再還沒有被始化或還沒有
          調(diào)構(gòu)造方法之前又被別的線程引用.因?yàn)楫?dāng)一個(gè)線程在執(zhí)行賦值的時(shí)候是原子性的操作,其它線程的引用操作也是原子性的操作?的,在賦值操作沒有完成之前其它線程根本不可能見到"分配了空間卻沒有
          初始化或沒有調(diào)用構(gòu)造方法"的這個(gè)對(duì)象.

          不知道什么原因,這樣的一個(gè)例子從它誕生開始竟然是所有人都相信了,也許有人責(zé)疑過但我不知道.如果你有足
          夠的基礎(chǔ)知識(shí),就不必跟著別人的感覺走!

          因?yàn)檫@是一個(gè)最最基礎(chǔ)的模式,暫時(shí)不介紹它與其它模式的關(guān)系.在以后介紹其它模式時(shí)反過來再和它進(jìn)行比較.

          而一些復(fù)雜的模式都是在這個(gè)簡單的模式的基礎(chǔ)上延伸的.

          posted on 2006-12-16 14:18 風(fēng)人園 閱讀(497) 評(píng)論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 阳东县| 商城县| 眉山市| 大化| 冀州市| 威宁| 洪雅县| 安平县| 遂平县| 肃南| 满洲里市| 澄江县| 成都市| 东方市| 札达县| 称多县| 青海省| 剑川县| 黎平县| 扶沟县| 宁德市| 涞源县| 贡嘎县| 防城港市| 田阳县| 岳阳县| 安国市| 庆阳市| 获嘉县| 稷山县| 阳春市| 准格尔旗| 合水县| 密云县| 连州市| 海丰县| 德江县| 布尔津县| 阿鲁科尔沁旗| 德格县| 微山县|