现在开始进入线程编程中最重要的话题—数据同步,它是线程编程的核心,也是难点,就算我们理解了数据同步的基本原理,但是我们也无法保证能够写出正确的同步代码,但基本原理是必须掌握的。
要想理解数据同步的基本原理,首先就要明白,为什么我们要数据同步?
public class CharacterDisplayCanvas extends JComponent implements
CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight;
public CharacterDisplayCanvas() {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
}
public CharacterDisplayCanvas(CharacterSource cs) {
this();
setCharacterSource(cs);
}
public void setCharacterSource(CharacterSource cs) {
cs.addCharacterListener(this);
}
public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
}
public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
}
protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0) {
return;
}
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
}
}
仔细查看上面的代码,我们就会发现,有两个方法的前面多了一个新的关键字:synchronized。让我们看看这两个方法为什么要添加这个关键字。
newCharacter()用于显示新字母,而paintComponent()负责调整和重画canvas。这两个方法存在着race condition,也就是竞争,因为它们访问的是同一份数据,最重要的是它们是由不同的线程所调用的,这就导致我们无法保证它们的调用是按照正确的顺序来进行,可能在newCharacter()方法未被调用前paintComponent()方法就已经重新绘制canvas。
之所以产生竞争,除了这两个方法访问的是同一份数据之外,还和它们是非automic有关。我们在初中的时候都学过,原子曾经被认为是最小单元,不可分的,哪怕现在已经证明这是不正确的,但原子不可分的概念在计算机这里保留了下来。 一个程序如果被认为是automic,那么就表示它是无法被中断的,不会有中间状态。使用synchronized,就能保证该方法无法被中断,那么其他线程就无法在该方法没有完成前调用它。
阅读全文
最新评论