臨界區模式 Critical Section Pattern 是指在一個共享范圍中只讓一個線程執行的模式.
它是所有其它多線程設計模式的基礎,所以我首先來介紹它.
把著眼點放在范圍上,這個模式叫臨界區模式,如果把作眼點放在執行的線程上,這個模式就叫
單線程執行模式.
首先我們來玩一個鉆山洞的游戲,我 Axman,朋友 Sager,同事 Pentium4.三個人在八角游樂場
循環鉆山洞(KAO,減肥訓練啊),每個人手里有一個牌子,每鉆一次洞口的老頭會把當前的次序,
姓名,牌號顯示出來,并檢查名字與牌號是否一致.
OK,這個游戲的參與者有游樂場老頭Geezer,Player,就是我們,還有山洞 corrie.

public class Geezer
{

public static void main(String[] args)
{

System.out.println("預備,開始!");
Corrie c = new Corrie(); //只有一個山洞,所以生存一個實例后傳給多個Player.
new Player("Axman","001",c).start();
new Player("Sager","002",c).start();
new Player("Pentium4","003",c).start();
}
}
這個類暫時沒有什么多說的,它是一個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);
}
}
}

在這里,我們把成員字段都設成final的,為了說明一個Player一旦構造,他的名字和牌號就不能改
變,簡單說在游戲中,我,Sager,Pentium4三個人不會自己偷偷把自己的牌號換了,也不會偷偷地去
鉆別的山洞,如果這個游戲一旦發生錯誤,那么錯誤不在我們玩家.
import java.util.*;

public class Corrie
{
private int count = 0;
private String name;
private String number;
private HashMap lib = new HashMap(); //保存姓名與牌號的庫


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());
}
}

這個類中增加了一個lib的HashMap,相當于一個玩家姓名與牌號的庫,因為明知道Corrie只有一個實例,
所以我用了成員對象而不是靜態實例,只是為了能在構造方法中初始化庫中的內容,從真正意義中說應
該在一個輔助類中實現這樣的數據結構封裝的功能.如果不提供這個lib,那么在check的時候就要用

if(name.equasl("Axman"))
{
if(!number.equals("001")) //出錯
}
else if 
.

這樣復雜的語句,如果player大多可能會寫到手抽筋,所以用一個lib來chcek就非常容象.
運行這個程序需要有一些耐心,因為即使你的程序寫得再差在很多單線程測試環境下也能可是正確的.
而且多線程程序在不同的機器上表現不同,要發現這個例子的錯識,可能要運行很長一段時間,如果你的
機器是多CPU的,那么出現錯誤的機會就大好多.
在我的筆記本上最終出現錯誤是在11分鐘以后,出現的錯誤有幾鐘情況:
1: ERR:Axman(003)
2: ERR:Sager(002)
第一種情況是檢查到了錯誤,我的牌號明明是001,卻打印出來003,而第二種明明沒有錯誤,卻打印了錯誤.
事實上根據以前介紹的多線程知識,不難理解這個例子的錯誤出現,因為into不是線程安全的,所以在其中
一個線程執行this.name = "Axman"; 后,本來應該執行this.numner="001",卻被切換到另一個線程中執行
this.number="003",然后又經過不可預知的切換執行其中一個的if(this.lib.get(name).equals(number))
而出現1的錯誤,而在打印這個錯誤時因為display也不是線程安全的,正要打印一個錯誤的結果時,由于
this.name或this.number其中一個字段被修改卻成了正確的匹配而出現錯誤2.
另外還有可能會出現序號顛倒或不對應,但這個錯誤我們無法直觀地觀察,因為你根本不知道哪個序號"應該"
給哪個Player,而序號顛倒則有可能被滾動的屏幕所掩蓋.
[正確的Critical Section模式的例子]
我們知道出現這些錯誤是因為Corrie類的方法不是線程安全的,那么只要修改Corrie類為線程安全的類就行
了.其它類則不需要修改,上面說過,如果出現錯誤那一定不是我們玩家的事:
import java.util.*;

public class Corrie
{
private int count = 0;
private String name;
private String number;
private HashMap lib = new HashMap(); //保存姓名與牌號的庫


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());
}
}


運行這個例子,如果你的耐心,開著你的機器運行三天吧.雖然測試100天并不能說明第101天沒有出錯,
at least,現在的正確性比原來那個沒有synchronized 保護的例子要可靠多了!
到這里我們對Critical Section模式的例程有了直觀的了解,在詳細解說這個模式之前,請想一下,test
方法安全嗎?為什么?