多線程編程的設(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