2013-08-18 59 views
0

我是新手,当发现问题时发现并发和不确定自己时,我正在查看相当成熟的代码库,并找到了下面的代码(为了简洁起见),我相信易受数据种族:多线程代码中的非易失性状态标志

public class Example extends Thread { 
    boolean condition = false; 

    public void run() { 
     while (true) { 
      synchronized (this) { 
       try { 
        while(condition) wait(); 
       } 
       catch (InterruptedException e) { /*for brevity*/ } 
      } 

      // non-blocking computation 
     } 
    } 

    public void setTrue() { condition = true; } 

    public void setFalse() { 
     synchronized (this) { 
      condition = false; 
      this.notifyAll(); 
     } 
    } 
} 

据我理解,因为即使在synchronized块,编译器不会发出任何内存屏障condition必须是挥发性的;如果这是一个不稳定的商店conditionsetTrue编译器会发出StoreEnter.

我是否有权相信上述容易受到数据竞争?如果是的话,我怎么能通过一个例子来见证数据竞赛(而不是简单地知道JMM提供的保证)。线程中随机调用setTrue的简单测试不会发现数据竞争。

此外,我相信使用notifyAll在这里是过度杀伤,因为有一个条件来检查,只有一个线程将永远等待它,对吗?

谢谢。

+0

除非您确实有理由,否则不要扩展Thread类。最好是实现Runnable接口并向Thread构造函数提供你的类的一个实例。 – gparyani

回答

3

就我所知,条件必须是易失性的,因为即使使用同步块,编译器也不会发出任何内存障碍;如果它是一个不稳定的存储区以在setTrue中进行调节,则编译器会发出StoreEnter。

这是不正确的。在​​块中使用共享变量时,对于使用相同锁定的相同变量的其他线程,您的代码将是线程安全的。如果需要记忆障碍,那么它们将被使用。

但是,你对我们的代码是是不正确因为setTrue()方法更新​​块之外的标志。


我说得对不对相信上面的易受数据争?

是的......有点。情景如下:

  1. 条件是false
  2. 一些其他线程调用setTrue它将条件变量设置为其缓存中的true。但由于setTrue方法不使用​​,因此不存在写入障碍,也不会刷新到主内存。
  3. “示例”线程从主内存(仍然是false)获取最新的提交值,并且不会按照它应该执行的操作等待。

而且,我认为使用notifyAll的是这里矫枉过正,因为有一个条件检查,只有一个线程将等待就可以了,对不对?

它可以替换为notify() ......如果这是你的意思。但说实话,你使用通知的风格并没有真正的区别。


你的评论:

我的意思是,编译器会不会认为有必要在这种情况下提交内存屏障。

也许吧。但“monitorenter”和“monitorexit”指示隐含地涉及记忆障碍。

和:

岂不也是正确的,如果条件为挥发性?

如果你在谈论使用volatile和​​,然后是这将是正确的......虽然volatile是多余的(假设setTrue bug修复。)

如果你是在谈论只有volatile,那么没有。您只能使用volatile来实现高效的“等待条件变量”。问题是“读/测试/等待”或“写/通知”序列都不能原子执行;即没有比赛条件的可能性。

此外,如果不使用原始对象互斥对象或Lock对象,则无法执行与wait/notify等效的操作。

+0

我的意思是编译器不会认为有必要在这种情况下提交内存屏障。如果情况不稳定,这也不正确吗? – bluth

0

我是否有权相信上述情况容易发生数据竞争?

不这么认为。条件不重要,它只允许该方法避免等待。它的设置方式也并不重要。它不需要变化,因为它对一个对象是本地的。

而且,我认为使用notifyAll的是这里矫枉过正,因为有 一个条件检查,只有一个线程将等待就可以了, 吧?

notifyAll的是好的,而只有一个线程在等待该方法可以有许多其他线程正在等待,或者等待时,线程。