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

问题描述 投票:0回答:5

我的建筑师总是这么说

从不在布尔上同步

我不明白为什么,如果有人能用一个例子来解释为什么这不是一个好的做法,我将不胜感激。 参考示例代码

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
      }
   }
}
java multithreading synchronization boolean
5个回答
78
投票

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

你应该总是

synchronize
在一个 常量对象实例上。如果您同步了您正在分配的任何对象(即将对象更改为新对象),那么它不是常量,不同的线程将在不同的对象 instances 上同步。因为它们在不同的对象实例上同步,所以多个线程将同时进入受保护的块并且会发生竞争条件。这与在
Long
Integer
等上同步的答案相同。

// this is not final so it might reference different objects
Boolean isOn = true;
...
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;

更糟糕的是,通过自动装箱 (

Boolean
) 创建的任何
isOn = true
Boolean.TRUE
(或
.FALSE
)是同一个对象,它是
ClassLoader
中跨越 all objects 的单例。您的锁对象应该是它所使用的类的本地对象,否则如果其他类犯了同样的错误,您将锁定在其他类可能在其他锁情况下锁定的同一个单例对象上。

如果你需要锁定一个布尔值,正确的模式是定义一个

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


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

这是个糟糕的主意。

isOn
将引用与公开可用的
Boolean.FALSE
相同的对象。如果任何其他写得不好的代码也决定锁定这个对象,两个完全不相关的事务将不得不相互等待。

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

enter image description here


1
投票

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


1
投票

所有包装类都是不可变的。人们不应该同步它们的主要原因之一。

就好像 2 个线程在包装类对象上同步,其中一个修改了它的值,它将在一个新的/修改过的对象上同步,两个线程将在 2 个不同的对象上同步。所以,同步的整个目的就失去了。


-3
投票

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

我想补充的是: 你的架构师是对的,如果从

Boolean
的角度来看是immutable,为什么要同步它?但是多线程比较复杂,要看场景。

© www.soinside.com 2019 - 2024. All rights reserved.