Java 设置布尔值是否需要临界截面?

Java 设置布尔值是否需要临界截面?,java,synchronization,Java,Synchronization,假设我们有n个线程访问这个函数,我听说即使布尔值只是一点翻转,这个过程也不是原子的。在该函数中,opening=true是否需要包装在同步中开始是该类的成员 boolean opening = false; public void open() { synchronized (this) { while (numCarsOnBridge != 0 || opening || closing) { // Wait if cars on bridg

假设我们有n个线程访问这个函数,我听说即使布尔值只是一点翻转,这个过程也不是原子的。在该函数中,
opening=true
是否需要包装在同步中<代码>开始是该类的成员

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
        }
    }

}

是的,对布尔值的并发修改需要同步,请使用原子布尔或同步构造

此外,如果不拥有监视器,则无法在此处调用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个理由将其包装到同步块中。

是的,对布尔值的并发修改需要同步,请使用原子布尔或同步构造

此外,如果不拥有监视器,则无法在此处调用notifyAll():

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

因此,您已经有两个理由将其包装在同步块中。

实际上
opening=true
是原子的-它只是不生成所谓的内存屏障

您应该使
打开
(以及
关闭
)不稳定

volatile boolean opening = false;

这将在每次
打开
更改时强制设置内存屏障,从而确保刷新
打开
变量的任何缓存。

实际上
打开=真
是原子的-它只是不生成所谓的内存屏障

您应该使
打开
(以及
关闭
)不稳定

volatile boolean opening = false;

这将在每次
打开
更改时强制设置一个内存屏障,从而确保清除
打开
变量的任何缓存。

关键部分是必要的。为了解释这一点,我们需要从原子性的角度来思考。具体来说,我们需要标志(
打开
)上的读写操作是单个原子操作(原子的意思是,它们发生在单个不可分割的步骤中)

考虑这个简化的例子

if (flag) //read
{
      flag = false; //write
      foo(); //arbitrary operation
}
在本例中,读取发生,然后写入发生,其间任何事情都可能发生(例如,线程2出现并在线程1之前看到
true
,将值设置为
false
,在这种情况下,foo()将被调用两次)

要解决这个问题,我们需要确保读写都在一个步骤中完成。我们可以通过将两者放在同一个同步块中来实现这一点

synchronized(monitor)
{ 
   if (flag) //read
   {
      flag= false; //write
      foo(); //some arbitrary operation
   }   
}
由于同步块中的所有内容都被视为一个巨大的原子操作,因此读/写也是原子操作,线程安全性也得到了实现(因此,每次标志设置为true时,单个线程只会调用一次foo)


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关键字快得多。

关键部分是必需的。为了解释这一点,我们需要从原子性的角度来思考。具体来说,我们需要标志(
打开
)上的读写操作是单个原子操作(原子的意思是,它们发生在单个不可分割的步骤中)

考虑这个简化的例子

if (flag) //read
{
      flag = false; //write
      foo(); //arbitrary operation
}
在本例中,读取发生,然后写入发生,其间任何事情都可能发生(例如,线程2出现并在线程1之前看到
true
,将值设置为
false
,在这种情况下,foo()将被调用两次)

要解决这个问题,我们需要确保读写都在一个步骤中完成。我们可以通过将两者放在同一个同步块中来实现这一点

synchronized(monitor)
{ 
   if (flag) //read
   {
      flag= false; //write
      foo(); //some arbitrary operation
   }   
}
由于同步块中的所有内容都被视为一个巨大的原子操作,因此读/写也是原子操作,线程安全性也得到了实现(因此,每次标志设置为true时,单个线程只会调用一次foo)


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关键字快得多。

Volatile只确保对以前的读写进行排序。volatile read(输入if条件)不会对后续写入的顺序(在if条件内)产生任何保证,因此,如果我们只使
打开
volatile,两个线程在可以重置标志之前可能仍会进入相同的if条件。@KookieMonster-您是正确的-我正在尝试确保代码按预期运行(即其他线程可以看到
opening=true
)。代码确实还有其他漏洞,尤其是围绕此操作的竞争。虽然设置布尔值本身确实是一个原子操作,但在这种情况下,将注意力放在易失性部分而不是关键部分是错误的,这个答案可能会造成混乱。Volatile只确保以前读写的顺序。volatile read(输入if条件)不会对后续写入的顺序(在if条件内)产生任何保证,因此,如果我们只使
打开
volatile,两个线程在可以重置标志之前可能仍会进入相同的if条件。@KookieMonster-您是正确的-我正在尝试确保代码按预期运行(即其他线程可以看到
opening=true
)。代码确实还有其他漏洞,尤其是围绕此操作的竞争。虽然设置布尔值本身确实是一个原子操作,但注意