Java 使用AtomicBoolean是否有效地替换了同步块?

Java 使用AtomicBoolean是否有效地替换了同步块?,java,locking,atomic,synchronized,atomicboolean,Java,Locking,Atomic,Synchronized,Atomicboolean,考虑两个不能同时执行的方法a()和b()。 同步关键字可用于实现此目的,如下所示。我可以按照下面的代码使用AtomicBoolean实现相同的效果吗 final class SynchonizedAB { synchronized void a(){ // code to execute } synchronized void b(){ // code to execute } } 尝试使用AtomicBoolean实现与上述相同的效果: final class AtomicA

考虑两个不能同时执行的方法a()和b()。 同步关键字可用于实现此目的,如下所示。我可以按照下面的代码使用AtomicBoolean实现相同的效果吗

final class SynchonizedAB {

synchronized void a(){
   // code to execute
}

synchronized void b(){
  // code to execute
}

}
尝试使用AtomicBoolean实现与上述相同的效果:

final class AtomicAB {

private AtomicBoolean atomicBoolean = new AtomicBoolean();

void a(){
   while(!atomicBoolean.compareAndSet(false,true){

  }
  // code to execute
  atomicBoolean.set(false);
}

void b(){
    while(!atomicBoolean.compareAndSet(false,true){

   }
     // code to execute
     atomicBoolean.set(false);
    }

 }

不,因为
同步
将阻塞,而使用
原子布尔
时,您将忙于等待


两者都将确保一次只有一个线程执行该块,但您是否希望让CPU在while块上旋转?

这取决于您计划使用原始同步版本的代码实现什么。如果在原始代码中添加synchronized只是为了确保在a或b方法中一次只存在一个线程,那么对我来说,这两个版本的代码看起来很相似

然而,正如卡亚曼所提到的,两者之间几乎没有什么区别。此外,为了添加更多的差异,使用同步块,您将获得内存屏障,而原子CAS循环将错过该屏障。但是如果方法的主体不需要这样的障碍,那么这种差异也会被消除


原子cas循环在个别情况下是否优于同步块,只有性能测试才能判断,但这是在并发包的多个位置采用的相同技术,以避免块级别的同步。

从行为角度来看,这似乎部分替代了Java的内置同步(监视器锁)。特别是,它似乎提供了正确的互斥,这正是大多数人在使用锁时所追求的

它似乎还提供了适当的内存可见性语义。类的
原子*
家族具有与
volatile
相似的内存语义,因此释放其中一个“锁”将提供与另一个线程获取“锁”的“先发生后发生”关系,这将提供您想要的可见性保证

这与Java的
synchronized
块的不同之处在于,它不提供异常情况下的自动解锁。要获得与这些锁相似的语义,必须将锁和用法包装在try-finally语句中:

void a() {
    while (!atomicBoolean.compareAndSet(false, true) { }
    try {
        // code to execute
    } finally {
        atomicBoolean.set(false);
    }
}
(与
b
类似)

这个构造似乎提供了类似于Java内置监视器锁的行为,但总的来说,我觉得这是一个误导。从您对它的评论来看,您似乎对避免阻塞线程的操作系统开销感兴趣。发生这种情况时肯定会有开销。但是,Java的内置锁经过了大量优化,在短期争用的情况下提供了非常便宜的无争用锁、有偏锁和自适应自旋循环。在许多情况下,最后一次尝试是避免操作系统级阻塞。通过实现自己的锁,您就放弃了这些优化


当然,你应该进行基准测试。如果您的性能受到操作系统级阻塞开销的影响,可能是您的锁太粗糙了。减少锁定或拆分锁定的数量,与尝试实现自己的锁相比,这可能是一种更有效的减少争用开销的方法。

是的,我宁愿旋转以降低停止/启动线程的操作系统调用成本,因为我使用此方法的情况是,执行的代码速度很快,因此不会旋转很长时间。@aranhakki线程不会停止/启动,线程将被阻塞。有一个显著的区别。好的,很有趣,你能解释一下区别吗?我想你应该知道,调用并不涉及操作系统,因此代价是JVM调用而不是操作系统调用?停止和启动线程意味着销毁和创建一个新线程,而阻塞线程只会让现有线程等待,直到它被唤醒并计划再次运行。您应该谨慎地假设使用自旋锁会带来任何性能好处。一些java.util.concurrent类确实使用了这种机制,因此它有一定的时间和地点,但它是否是这里的最佳解决方案还不确定。至少要对其进行适当的性能测试。您必须权衡方法1(同步)中上下文切换的成本与方法2(CAS)中不必要的旋转的成本。有了CAS,线程就失去了做一些有意义的事情的机会,而是在while循环中旋转。几乎无限的while循环不会产生CPU或其他开销吗?@Jus12是的,这种“旋转等待”或“旋转锁定”技术可能会产生一些开销。但是阻塞一个线程,安排它被通知,然后再次唤醒它,也会导致开销和延迟。这里的冒险在于,通过CAS循环的次数可能很小,因此与阻塞和唤醒相比,开销很小。