【问题标题】:Concurrent race condition?并发竞争条件?
【发布时间】:2012-06-12 11:08:31
【问题描述】:

此类扩展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) {
                //...
            }
        }
    }
}

它遵循Observer Pattern。这个类将订阅ConfigurationController,每当任何配置参数发生变化时,它都会通过notifyConfiguration(...)方法通知他。

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

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

【问题讨论】:

    标签: java concurrency


    【解决方案1】:

    Q1:这会成为竞争条件吗?

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

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

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

    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 调用inside 设置为同步块,则无法完全消除竞争条件。 (这会导致调用 notifyConfiguration 的线程阻塞可能无限长的时间。坏主意。)


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

    【讨论】:

    • 或者制作refreshMSvolatile
    • @pingw33n - 如果你使用volatile,还有一个最小的竞争条件。
    • 在 OP 的问题中,除了 try...catch 之外,while 循环内没有其他代码。否则你是对的。
    • 好吧,陈旧的价值并没有那么糟糕,问题来的时候甚至没有价值,只是垃圾。我认为用 Java 写 int 是原子的,但想象一下它不是。您最终可能会读取一个值,其中一半位代表新值,另一半位代表旧值!好可怕……
    • @pingw33n 正如我对 Stephen C 所说,我认为 volatile 不能阻止阅读垃圾。
    【解决方案2】:

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

    class Controller extends Thread implements ConfigurationObserver{
    
        private volatile int refreshMS;
        //...
     }
    

    引用Brian Goetz's Managing Volatility

    易失性变量共享同步的可见性特征,但 没有原子性特征。这意味着线程将 自动查看 volatile 变量的最新值。

    这意味着在极少数情况下可以使用volatile 代替synchronized。但幸运的是这是其中之一,因为int 是原子写入的(没有线程可以读取它的值,而其他线程正在修改它)。

    因此,正如 Stephen C 所说,这并没有消除竞争条件,它只会让这种情况非常罕见。在我的情况下,如果 refresMS 被正在运行的线程读取为旧值并不是什么大问题(如果它几乎不会发生)。

    【讨论】: