2016-04-21 66 views
0

假设我们有n个线程可以访问这个函数,我听说即使布尔值只是一个点翻转,这个过程也不是原子的。在这个函数中,opening = true是否需要包装在一个同步? opening是该类的成员。是否需要设置布尔值的关键部分?

boolean opening = false; 

public void open() { 

    synchronized (this) { 
     while (numCarsOnBridge != 0 || opening || closing) { 
      // Wait if cars on bridge, opening or closing 
      try { 
       // Wait until all cars have cleared the bridge, until bridge opening 
       wait(); 
      } catch (InterruptedException e) {} 
     } 
     // By now, no cars will be under the bridge. 
    } 

    if (!opening) { 
     opening = true; // Do we need to wrap this in a synchronize? 
     // notifyAll(); // Maybe need to notify everyone that it is opening 
     try { 
      sleep(60); // pauses the current thread calling this 
     } catch (InterruptedException e) {} 

     synchronized (this) { 
      drawBridgeClosed = false; // drawBridge is opened 
      opening = false; 
      notifyAll(); // Only notify all when state has fully changed 
     } 
    } 

} 

回答

1

是的,对一个布尔值的并发修改需要同步,使用AtomicBoolean或synchronized结构。

而且,你不能没有在这里拥有一个监控器调用notifyAll的():

if (!opening) { 
    opening = true; // Do we need to wrap this in a synchronize? 
    // notifyAll(); // Maybe need to notify everyone that it is opening 

所以,你已经有2个原因,包装在synchronized块。

1

其实opening = true原子 - 它只是不会产生所谓的内存屏障

您应该制作opening(和closing就此事)volatile

volatile boolean opening = false; 

这每次将迫使存储器屏障opening的变化,从而确保opening变量的任何cacheing被刷新。

+0

易失性只确保为先前的读取和写入排序。 volatile读取(进入if条件)不会对后续写入的顺序(在if条件中)产生任何保证,因此,如果我们只让'opening'易失性,两个线程仍可能进入相同if条件之前可以重置标志。 – KookieMonster

+0

@KookieMonster - 你是对的 - 我试图确保代码的功能如预期的那样(即'opening = true'对其他线程可见)。代码确实还有其他漏洞,特别是围绕此操作的种族。 – OldCurmudgeon

+0

虽然设置布尔本身本身就是一个原子操作的确是真的,但在这种情况下,注意力放在易变的而不是关键的部分上,这是imho的误导,而这个答案可能会造成混淆。 – kRs

1

临界区是必要的。为了解释这一点,我们需要考虑原子性。具体来说,我们需要对标志(opening)进行读写操作,使其成为单个原子操作(原子意思是它们在一个不可分割的步骤中发生)。

考虑这个简化的例子;

if (flag) //read 
{ 
     flag = false; //write 
     foo(); //arbitrary operation 
} 

在这个例子中,读取操作,然后将写操作发生之后,任何事情都有可能之间发生(例如,线程#2磨磨蹭蹭之前线程#看到true 1套价值false,在这种情况下foo()会被调用两次)。

要解决这个问题,我们需要确保读取和写入都在一个步骤中进行。我们可以通过将两者都放在同一个同步块中来做到这一点;

synchronized(monitor) 
{ 
    if (flag) //read 
    { 
     flag= false; //write 
     foo(); //some arbitrary operation 
    } 
} 

由于同步块内的所有内容被认为是一个伟大的大原子操作,读/写也是原子和线程安全的实现(这样foo将只能通过每次单个线程的标志一度被称为设置为true)。


的AtomicBoolean(如在对方的回答提到的),也可在这种情况下使用的,因为这类提供的操作来读取并以相同的步骤写。例如;

static AtomicBoolean flag = new AtomicBoolean(false); 

public void run() 
{ 
    if (flag.getAndSet(false)) //get and then set the flag 
    { 
     //the flag is now set to 'false' 
     //and we will enter this section if it was originally 'true' 
     foo(); 
    } 
} 

作为一个方面说明,“getAndSet”操作在功能上与此类似;

public boolean getAndSet(boolean newVal) 
{ 
    synchronized(this) 
    { 
     boolean val = oldVal; 
     oldVal = newVal; 
     return val; 
    } 
} 

然而,它以一种高度优化的方式来实现,所以通常比使用synchronized关键字快得多。