这个类中增加了一个lib的HashMap,相当于一个玩家姓名与牌号的库,因为明知道Corrie只有一个实例,
所以我用了成员对象而不是静态实例,只是为了能在构造方法中初始化库中的内容,从真正意义中说应
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
方法安全吗?为什么?
