看到《Java Threads》第5章,介绍了JDK 1.5新加的一些所谓原子类(Atomic
Classes),总感觉有点为原子而原子,实际操作中,又有多少人会为了少许的性能提升而刻意去用这些别扭的操作而放弃直观的synchronize关
键字或者Lock类呢?不过,这里不是想讨论这个,而是当其用Atomic
Classes来改造它的打字程序后,解释用原子类只是保证类似递增、递减、赋值等操作的原子性,而不能保证其所在的方法一定是线程安全的,然后说,有可
能按键事件的处理可能需要等待resetScore()处理完才能执行,而这会导致错误的评分(被当成多敲了键)。由于前几章的内容相对比较简单易懂,所
以也没有很仔细的运行那些例子。这里为了验证一下,就运行了一下第4章的例子,然后发现,基本上第一次的评分总是错的。这就引起了我的注意,因为,一般情
况下,如果是race
condition导致的错误是很难重现的,这么明显的错误很可能是程序逻辑上的错误。仔细看了一下代码,发现在start按钮的事件处理方法里,有下面
这样一段代码:
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
score.resetScore();
}
});
注意重置成绩的调用放在了最后,此时,随机生成字符的线程应该被唤醒并产生了第一个字符,然后,resetScore()将需要输入的字符又设成了-1,
所以,当你第一次输入字符时,总是被认为是多击了一次键而扣1分:(。既然这样,那停止然后再启动也应该会发生这个错误啊。而事实上的确是这样。我想,这
不应该看做是race
condition吧,有什么样的同步技术能够避免这个问题呢?除非另外弄个标志,当成绩没有被重置前,不能产生第一个字符。当然,这是不需要的,只要将
score.resetScore()放到第一句就可以了。
然
后又运行了第3章的例子,发现基本上没有这个问题。难道第3章的代码是正确的?打开源代码一看,重置成绩的方法还是放在最后,那这里为什么又是正确的呢?
我想,大约是第3章的例子中,每次点击start按钮,都重新创建一个线程对象的原因吧。由于创建对象和初始化线程需要一定的时间,刚好给了主线程重置成
绩的机会。
不知道作者有意为之呢,还是疏忽,不过,这样的错误不能算是race condition的例子。