Java 中的自旋锁和适应性自旋锁!

阅读过上篇文章的网友,可能会发现上一篇文章中已经出现了一个自旋锁!

什么?怎么可能?你在上一篇文章中连一个自旋锁的字都没提,怎么可能有自旋锁(欺骗我农村来的是吧,一定是)?

我怎么可能会骗你呢?就算骗了你,也骗不了其他人,那么多读者,大家都比我知识还深厚!更何况我自己打自己脸!

从上一篇文章中,我们知道 AtomicInteger 是基于 CAS 实现的自旋锁。具体怎么自旋的呢?

// JDK 8
// AtomicInteger 自增方法
public final int incrementAndGet() {
  return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe.class
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 方法。整个“比较+更新”操作封装在 compareAndSwapInt() 中,它就相当于自旋的去重试这个操作!

好了,下面我们开始真正的自旋锁吧!

自旋锁

在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃 CPU 的执行时间,看看持有锁的线程是否很快就会释放锁。

而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

下面我们在通过一张流程图,从感观上认识认识自旋锁!

Java 自旋锁 VS 适应性自旋锁

自旋锁的优点

自旋锁肯定是有优点的,不然肯定不会采用它。

阻塞或唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

所以自旋锁就可以避免 CPU 切换带来的性能、时间等影响。

自旋锁的缺点

任何技术都有它的两面性,有优点也就以为着有缺点。

自旋锁本身的缺点是,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是 10 次,可以使用 -XX:PreBlockSpin 来更改)没有成功获得锁,就应当挂起线程。

自旋锁的实现原理同样也是 CAS,AtomicInteger 中调用 unsafe 进行自增操作的源码中的 do-while 循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。

AtomicInteger getAndAddInt

自旋锁在 JDK1.4.2 中引入,使用 -XX:+UseSpinning 来开启。JDK 6 中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。

适应性自旋锁

自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

Java 中的自旋锁和适应性自旋锁!

: » Java 中的自旋锁和适应性自旋锁!

原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/251929.html

(0)
上一篇 2022年5月3日
下一篇 2022年5月3日

相关推荐

发表回复

登录后才能评论