为什么在布尔值上进行同步不是一种好的做法?
为什么在布尔值上进行同步不是一种好的做法?
我的建筑师总是说:
永远不要在布尔变量上同步
我无法理解其中的原因,如果有人能够用示例来解释为什么这样做不是一个好习惯,我将非常感激。
private Boolean isOn = false; private String statusMessage = "I'm off"; public void doSomeStuffAndToggleTheThing(){ // 进行一些操作 synchronized(isOn){ if(isOn){ isOn = false; statusMessage = "I'm off"; // 进行关闭操作的其他操作 } else { isOn = true; statusMessage = "I'm on"; // 进行打开操作的其他操作 } } }
在Java中,使用synchronized关键字可以在多线程环境下保证代码的同步执行。然而,它并不适合用于Boolean类型的变量上。为什么呢?
假设每个线程都是一条道路,语句(车辆)按顺序行驶。在某些情况下,可能会有一个交叉口,如果没有信号量,就可能发生碰撞。Java语言提供了一种内置的方式来描述这个问题:由于任何对象都可以作为一个交叉口,因此任何对象都有一个关联的监视器作为信号量。当在代码中使用synchronized时,你正在创建一个信号量,因此你必须对所有的道路(线程)使用相同的信号量。
因此,这个问题并不是特定于Boolean类型的,因为只有两个Boolean值存在。每当在实例变量上同步,然后将相同的变量指向不同的对象时,这个问题都会发生。因此,你的代码在布尔值上是错误的,但对于整数、字符串和任何对象来说,如果你不理解发生了什么,它同样是危险的。
那么,如何解决这个问题呢?解决方法很简单:不要在Boolean类型的变量上使用synchronized关键字。相反,可以使用其他对象来作为信号量,从而实现同步。例如,可以创建一个专门用于同步的对象,并使用它来控制代码的执行。下面是一个示例代码:
// 创建一个用于同步的对象 Object lock = new Object(); // 在代码块中使用同步 synchronized(lock) { // 执行需要同步的操作 }
通过这种方式,我们可以避免在Boolean类型的变量上使用synchronized关键字而导致的问题,并确保代码在多线程环境下的正确执行。
为什么在Boolean上进行同步不是一个好的做法?
在上面的代码中,使用了一个Boolean类型的变量isOn来进行同步操作。这是一个很糟糕的想法,因为isOn将引用与公开可用的Boolean.FALSE相同的对象。如果其他糟糕编写的代码也决定在此对象上进行锁定,两个完全无关的事务将不得不互相等待。
锁定是在对象实例上执行的,而不是在引用它们的变量上。这意味着不同的变量引用相同的对象实例时,它们将争夺锁定。
如果我们给出一个导致死锁条件的其他糟糕代码的示例,你能做到吗?
示例?任何在Boolean上进行同步的其他代码,即使它们在调用它时给它取了一个与isOn不同的名字。由于只有两个Boolean对象,所以所有这些线程将互相竞争。这是一个非常糟糕的情况。
这里真正的问题是这样。尽管我认为更一般的解释为什么在不完全控制的对象上进行同步是一个坏主意会更好 - 在单例上这样做只会使问题更加明显。
我喜欢这张图片!你不是为了回答这个问题而画的,对吗?
解决方法:避免在不能完全控制的对象上进行同步操作,以避免潜在的死锁问题。
为什么在Boolean上同步不是一个好的做法?
在Java中,synchronize关键字用于实现多线程同步,以确保多个线程对共享资源的访问是有序的。通常情况下,我们应该在一个常量对象实例上进行同步,以避免不同线程在不同的对象实例上进行同步,从而导致竞态条件的发生。
在这个问题中,如果我们在一个Boolean对象上进行同步,而这个Boolean对象是可变的(即在代码中会被赋值为一个新的对象),那么不同的线程将会在不同的对象实例上进行同步。这样,多个线程将同时进入同步块,导致竞态条件的发生。同样的问题也会出现在Long、Integer等类型的对象上。
更糟糕的是,通过自动装箱(autoboxing)创建的Boolean对象(例如isOn = true)实际上是与Boolean.TRUE(或.FALSE)相同的对象,它是ClassLoader跨所有对象的单例对象。如果我们在类之间共享相同的锁对象,那么就会出现多个类在不同锁情况下都在锁定相同的单例对象的情况。
因此,解决这个问题的正确方法是定义一个私有的final锁对象,该对象应该是本类局部的。或者,我们也可以考虑使用AtomicBoolean对象,这样就不需要在其上进行同步。
对于需要在boolean值周围进行锁定的情况,我们可以使用如下代码:
private final Object lock = new Object(); synchronized (lock) { ... }
或者,我们还可以考虑使用AtomicBoolean对象,这样就不需要在其上进行同步:
private final AtomicBoolean isOn = new AtomicBoolean(false); if (isOn.compareAndSet(false, true)) { statusMessage = "I'm now on"; } else { statusMessage = "I'm already on"; }
如果我们需要在切换on/off状态时使用多线程进行同步,仍然需要在锁对象上进行同步,以避免测试/设置竞态条件的发生:
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,除非我们在获取时也进行同步。
总结一下,同步Boolean对象不是一个好的做法,因为它会导致不同线程在不同对象实例上同步,从而引发竞态条件的发生。正确的解决方法是使用私有的final锁对象或者考虑使用AtomicBoolean对象。