Java并发编程中自旋锁会浪费 CPU 资源并导致错误


在并发编程中,自旋锁(spin locks )想必大家都不陌生。
自旋锁一个非常经典的使用场景是CAS(即比较和交换),是一种无锁的思想(说白了就是使用了无限循环),用来解决更新数据的问题高并发场景。

atomic包下的很多类,如AtomicInteger、AtomicLong、AtomicBoolean等,都是用CAS实现的。
我们以AtomicInteger类为例,它incrementAndGet不会每次都给变量加 1。

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

它的底层是用自旋锁实现的:

public final int getAndAddInt(Object var1, long var2, int var4) {
  int var5;
  do {
      var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

在do…while无限循环中,不断地进行数据的比较和交换。如果一直失败,就会一直重试。
在高并发的情况下,compareAndSwapInt大概率会失败,从而导致CPU不断自旋,严重浪费CPU资源。

那么,如果这个问题解决了呢?
解决方案是使用LockSupport类的方法parkNanos。

private boolean compareAndSwapInt2(Object var1, long var2, int var4, int var5) {
     if(this.compareAndSwapInt(var1,var2,var4, var5)) {
          return true;
      } else {
          LockSupport.parkNanos(10);
          return false;
      }
 }

当CAS失败时,调用LockSupport类的parkNanos方法进行睡眠,这相当于调用Thread.Sleep方法。

这样可以有效减少因频繁旋转而造成的CPU资源过度浪费的问题。