并发竞态条件?

4

这个类扩展了 Thread,一旦创建,线程就会启动。以下是代码:

class Controller extends Thread implements ConfigurationObserver{

    private int refreshMS;

    //...

    @Override
    public void notifyConfiguration(ConfigurationModel config) {
        refreshMS = config.getRefreshMs();
    }

    @Override
    public void run() {
        //...
        while (true) {

            try {
                Thread.sleep(refreshMS);
            } catch (InterruptedException ex) {
                //...
            }
        }
    }
}

这个类遵循观察者模式。该类将订阅ConfigurationController,每当配置参数发生更改时,通过notifyConfiguration(...)方法通知它。

让我有点不安的是refresMS属性。配置通过GUI(线程#1)更改,并影响Controller类(线程#2)的属性,该属性从该类的运行线程(线程#3)中读取。

Q1:这可能会成为竞态条件吗?
Q2:如果是,解决这个问题的最佳方法是什么?

2个回答

3

问题1:这是否可能成为竞态条件?

是的。有点像。 run() 方法最终可能会使用一个过时的 refreshMS 值。

问题2:如果是这样,解决这个问题的最佳方法是什么?

这样可以最小化竞态条件:

class Controller extends Thread implements ConfigurationObserver{
    private int refreshMS;
    public synchronized void notifyConfiguration(ConfigurationModel config) {
        refreshMS = config.getRefreshMs();
    }

    public void run() {
        while (true) {
            ...
            synchronized (this) {
                rms = refreshMS;
            }
            Thread.sleep(rms);
            ....
        }
    }
}

如果不将sleep调用放在同步块内部,就无法完全消除竞态条件。(这将导致调用notifyConfiguration的线程阻塞一段时间,这是个非常糟糕的想法。)


现在,这些都很好,但您还应该问自己竞态条件是否可能对应用程序的执行产生有害影响。


1
或将 refreshMS 声明为 volatile - pingw33n
@pingw33n - 如果你使用volatile,也会存在一种最小的竞态条件。 - Stephen C
在OP的问题中,除了try...catch之外,在while循环内没有其他代码。否则你是正确的。 - pingw33n
一个过期的值并不是那么糟糕,问题在于当它甚至不是一个值,而只是垃圾时。我认为在Java中写入int是原子性的,但想象一下如果不是这样会发生什么。你可能会读取一个值,其中一半位表示新值,另一半位表示旧值!这很可怕... - eversor
那么,同步和易失性方法在解决这个问题时完全一样吗? - eversor
显示剩余5条评论

2

我采用的解决方案是pingw33n建议的。使用关键字volatile

class Controller extends Thread implements ConfigurationObserver{

    private volatile int refreshMS;
    //...
 }

引用自Brian Goetz的《管理波动性》

易变变量与同步对象(synchronized)共享可见性特征,但没有原子性特征。这意味着线程将自动看到易变变量的最新值。

这意味着在非常少的情况下可以使用volatile代替synchronized。但幸运的是,这是其中之一,因为int具有原子写入的特性(在其他线程修改其值时,没有线程能够读取其值)。

因此,正如Stephen C所说,这并不消除竞争条件,只是使其发生的可能性变得非常小。在我的情况下,如果运行中的线程使用旧值读取了refresMS,那也没什么大不了的(如果这种情况几乎不会发生的话)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接