多線程編程的設(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è)人在八角游樂場(chǎng)
循環(huán)鉆山洞(KAO,減肥訓(xùn)練啊),每個(gè)人手里有一個(gè)牌子,每鉆一次洞口的老頭會(huì)把當(dāng)前的次序,
姓名,牌號(hào)顯示出來,并檢查名字與牌號(hào)是否一致.
OK,這個(gè)游戲的參與者有游樂場(chǎng)老頭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)就不能改
變,簡(jiǎn)單說在游戲中,我,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ū)懙迷俨钤诤芏鄦尉€程測(cè)試環(huán)境下也能可是正確的.
而且多線程程序在不同的機(jī)器上表現(xiàn)不同,要發(fā)現(xiàn)這個(gè)例子的錯(cuò)識(shí),可能要運(yùn)行很長(zhǎng)一段時(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)行三天吧.雖然測(cè)試100天并不能說明第101天沒有出錯(cuò),
at least,現(xiàn)在的正確性比原來那個(gè)沒有synchronized 保護(hù)的例子要可靠多了!
到這里我們對(duì)Critical Section模式的例程有了直觀的了解,在詳細(xì)解說這個(gè)模式之前,請(qǐng)想一下,test
方法安全嗎?為什么?
它是所有其它多線程設(shè)計(jì)模式的基礎(chǔ),所以我首先來介紹它.
把著眼點(diǎn)放在范圍上,這個(gè)模式叫臨界區(qū)模式,如果把作眼點(diǎn)放在執(zhí)行的線程上,這個(gè)模式就叫
單線程執(zhí)行模式.
首先我們來玩一個(gè)鉆山洞的游戲,我 Axman,朋友 Sager,同事 Pentium4.三個(gè)人在八角游樂場(chǎng)
循環(huán)鉆山洞(KAO,減肥訓(xùn)練啊),每個(gè)人手里有一個(gè)牌子,每鉆一次洞口的老頭會(huì)把當(dāng)前的次序,
姓名,牌號(hào)顯示出來,并檢查名字與牌號(hào)是否一致.
OK,這個(gè)游戲的參與者有游樂場(chǎng)老頭Geezer,Player,就是我們,還有山洞 corrie.










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

















在這里,我們把成員字段都設(shè)成final的,為了說明一個(gè)Player一旦構(gòu)造,他的名字和牌號(hào)就不能改
變,簡(jiǎn)單說在游戲中,我,Sager,Pentium4三個(gè)人不會(huì)自己偷偷把自己的牌號(hào)換了,也不會(huì)偷偷地去
鉆別的山洞,如果這個(gè)游戲一旦發(fā)生錯(cuò)誤,那么錯(cuò)誤不在我們玩家.




































這個(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í)候就要用







這樣復(fù)雜的語句,如果player大多可能會(huì)寫到手抽筋,所以用一個(gè)lib來chcek就非常容象.
運(yùn)行這個(gè)程序需要有一些耐心,因?yàn)榧词鼓愕某绦驅(qū)懙迷俨钤诤芏鄦尉€程測(cè)試環(huán)境下也能可是正確的.
而且多線程程序在不同的機(jī)器上表現(xiàn)不同,要發(fā)現(xiàn)這個(gè)例子的錯(cuò)識(shí),可能要運(yùn)行很長(zhǎng)一段時(shí)間,如果你的
機(jī)器是多CPU的,那么出現(xiàn)錯(cuò)誤的機(jī)會(huì)就大好多.
在我的筆記本上最終出現(xiàn)錯(cuò)誤是在11分鐘以后,出現(xiàn)的錯(cuò)誤有幾鐘情況:


第一種情況是檢查到了錯(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ò)誤那一定不是我們玩家的事:




































運(yùn)行這個(gè)例子,如果你的耐心,開著你的機(jī)器運(yùn)行三天吧.雖然測(cè)試100天并不能說明第101天沒有出錯(cuò),
at least,現(xiàn)在的正確性比原來那個(gè)沒有synchronized 保護(hù)的例子要可靠多了!
到這里我們對(duì)Critical Section模式的例程有了直觀的了解,在詳細(xì)解說這個(gè)模式之前,請(qǐng)想一下,test
方法安全嗎?為什么?
posted on 2008-01-06 22:20 々上善若水々 閱讀(495) 評(píng)論(0) 編輯 收藏 所屬分類: J2SE