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

Rac*_*hel 33 java multithreading boolean synchronize

我的建筑师总是说

永远不要在布尔上同步

我无法理解其中的原因,如果有人可以用一个例子来解释为什么它不是一个好的做法,我会非常感激. 参考样本代码

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
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

Gra*_*ray 62

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

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

// 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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

synchronized (lock) {
   ...
Run Code Online (Sandbox Code Playgroud)

或者您也应该考虑使用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";
}
Run Code Online (Sandbox Code Playgroud)

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

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
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,如果您希望statusMessage从其他线程访问它,那么它应该被标记为volatile除非您synchronize在获取期间.

  • 虽然这一切都是真实而重要的,但答案却缺少一个非常重要的部分:**永远不要对你无法控制的对象进行同步,特别是对整个架构中共享的单例进行同步.这将只工作一次,但只要其他人有同样的想法,你就会遇到大问题 (3认同)

McD*_*ell 19

private Boolean isOn = false;
public void doSomeStuffAndToggleTheThing(){
   synchronized(isOn){
Run Code Online (Sandbox Code Playgroud)

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

锁定在对象实例上执行,而不是在引用它们的变量上执行:

在此输入图像描述