自旋锁 VS 适应性自旋锁
堵塞或者notify一个Java线程需要操作系统切换CPU状态来完成(详情请参考11408)。这种状态切换需要耗费CPU时间。如果同步代码块种的内容过于简单。状态切换消耗的时间可能比用户代码执行的时间还要长。
在许多场景中,同步资源的锁定时间很短,为了这一段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果机器有多个CPU,能够让两个或以上的线程同时并行执行,就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快释放锁。
为了让当前线程"等一下" 我们需让当前线程进行自旋,如果在自旋完成后前面同步资源已经释放了锁。那么当前线程就可以不必堵塞而是直接获取同步资源。避免线程切换的开销。这就是自旋
自旋锁本身就有缺点。他不能代替堵塞。自旋等待虽然避免了线程切换的开销,但是他要占用CPU时间。如果锁被占用的时间很短。自旋等待的效果就会比较好。反之,如果锁被占用的时间很长。那么自旋的线程只会白白浪费CPU资源。所以 自旋等待的时间必须要有一定的限制。如果自旋超过一定的次数(JDK默认10次 参数PreBlockSpin)没有成功获得锁 就应该suspend
自旋锁的实现原理同样也是CAS。AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作。如果修改数值失败则通过循环来执行自旋。知道修改成功
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
适应性自旋锁
自适应意味着自旋的时间(次数)不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚陈工获得过锁。并且持有锁的线程正在进行中,那么虚拟机就会认为这次自旋也是有可能再次成功的,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁 自旋很少成功。那在以后尝试获取这个锁可以忽略掉自旋,直接堵塞。避免浪费CPU。
在自旋锁中。另有三种常见的锁形式:TicketLock CLHLock和MCSLock