为什么在布尔值上同步不是一个好习惯?

时间:2012-04-25 21:45:01

标签: java multithreading boolean synchronize

我的建筑师总是这么说

  

永远不要在布尔

上同步

我无法理解其原因,如果有人可以用一个例子来解释为什么它不是一个好的做法,我会非常感激。 Reference Sample Code

private Boolean isOn = false;
private String statusMessage = "I'm off";
public void doSomeStuffAndToggleTheThing(){

   // Do some stuff
   synchronized(isOn){
      if(isOn){
         isOn = false;
         statusMessage = "I'm off";
         // Do everything else to turn the thing off
      } else {
         isOn = true;
         statusMessage = "I'm on";
         // Do everything else to turn the thing on
      }
   }
}

5 个答案:

答案 0 :(得分:60)

  

我无法理解为什么我们应该“永远不会在布尔上同步”

您应该始终{em}常量对象实例上的synchronize 。如果您在分配的任何对象上进行同步(即将对象更改为新对象),则它不是常量,并且不同的线程将在不同的对象实例上进行同步。因为它们在不同的对象实例上同步,所以多个线程将同时进入受保护的块并且将发生竞争条件。这是在LongInteger等同步的同一答案。

// this is not final so it might reference different objects
Boolean isOn;
...
synchronized (isOn) {
   if (isOn) {
      // this changes the synchronized object isOn to another object
      // so another thread can then enter the synchronized with this thread
      isOn = false;

更糟糕的是(正如@McDowell指出的那样)通过自动装箱(Boolean)创建的任何isOn = true都是与Boolean.TRUE(或.FALSE)相同的对象这是所有对象ClassLoader的单身人士。你的锁对象应该是它所使用的类的本地对象,否则你将锁定其他类可能在其他锁定情况下锁定的同一单例对象,如果它们犯了同样的错误。

如果需要锁定布尔值,则正确的模式是定义private final锁定对象:

private final Object lock = new Object();
...

synchronized (lock) {
   ...

或者您也应该考虑使用AtomicBoolean对象,这意味着您可能根本不需要synchronize

private final AtomicBoolean isOn = new AtomicBoolean(false);
...

// if it is set to false then set it to true, no synchronization needed
if (isOn.compareAndSet(false, true)) {
    statusMessage = "I'm now on";
} else {
    // it was already on
    statusMessage = "I'm already on";
}

在你的情况下,因为看起来你需要用线程打开/关闭它然后你仍然需要在synchronize对象上lock并设置布尔值并避免测试/设置竞争条件:

synchronized (lock) {
    if (isOn) {
        isOn = false;
        statusMessage = "I'm off";
        // Do everything else to turn the thing off
    } else {
        isOn = true;
        statusMessage = "I'm on";
        // Do everything else to turn the thing on
    }
}

最后,如果您希望从其他线程访问statusMessage,则应将其标记为volatile,除非您在获取期间synchronize

答案 1 :(得分:18)

private Boolean isOn = false;
public void doSomeStuffAndToggleTheThing(){
   synchronized(isOn){

这是一个糟糕的主意。 isOn将引用与Boolean.FALSE相同的对象,该对象是公开的。如果任何其他编写错误的代码也决定锁定此对象,则两个完全不相关的事务将不得不相互等待。

锁定在object instances上执行,而不是在引用它们的变量上执行:

enter image description here

答案 2 :(得分:1)

我认为你的问题更多的是同步本身而不是同步和布尔。想象一下,每个Thread都是一条道路,声明(汽车)一个接一个地走。在某些时候可能存在交叉:没有信号量冲突可能发生。 Java语言有一种内置的方式来描述它:因为任何对象都可以是一个交集,所以任何对象都有一个相关的监视器作为信号量。当您在代码中使用synchronized时,您正在创建信号量,因此您必须对所有道路(线程)使用相同的信号量。所以这个问题并不是特定于布尔特定的,因为只存在两个布尔值,每次在实例变量上同步然后将同一个变量指向另一个对象时就会发生这个问题。所以你的代码错误的布尔,但如果你不了解正在发生的事情,那么对整数,字符串和任何对象都同样危险。

答案 3 :(得分:0)

所有包装器类都是不可变的。为什么不应该在它们上进行同步的主要原因之一。

好像两个线程在包装类对象上同步并且其中一个线程修改其值,它将在一个新的/修改的对象上同步,并且两个线程在两个不同的对象上同步。因此,失去了同步的全部目的。

答案 4 :(得分:-3)

编辑:格雷的回答是正确的。

我想要添加的是: 您的架构师是对的,如果从Boolean 不可变的角度来看,为什么要同步呢?但多线程很复杂,并且基于场景。