1.什么是自旋
自旋(Spinning)是一种在多线程环境下等待锁的技术。当一个线程尝试获取某个已被其他线程持有的锁时,该线程不会立即进入阻塞状态,而是会在一个循环中持续检查锁的状态,即“自旋”。如果在这个过程中锁被释放了,那么该线程就可以立即获取锁,从而避免了线程阻塞和上下文切换的开销。
2.锁升级过程的第一次自旋
第一次自旋发生在synchronized获取轻量级锁时,即当一个线程尝试获取一个被其他线程持有的轻量级锁时,它会自旋等待锁的持有者释放锁。
在OpenJDK8中,轻量级锁的自旋默认是开启的,最多自旋15次,每次自旋的时间逐渐延长。如果15次自旋后仍然没有获取到锁,就会升级为重量级锁。
3.锁升级过程中的第二次自旋
第二次自旋发生在synchronized 轻量级锁升级到重量级锁的过程中。即当一个线程尝试获取一个被其他线程持有的重量级锁时,它会自旋等待锁的持有者释放锁。
在OpenJDK8中,默认情况下不会开启重量级锁自旋。如果线程在尝试获取重量级锁时,发现该锁已经被其他线程占用,那么线程会直接阻塞,等待锁被释放。如果锁被持有时间很短,可以考虑开启重量级锁自旋,避免线程挂起和恢复带来的性能损失。
4.自适应自旋
在JDK6中之后的版本中,JVM引入了自适应的自旋机制。该机制通过监控轻量级锁自旋等待的情况动态调整自旋等待的时间。
如果自旋等待的时间很短,说明锁的竞争不激烈,当前线程可以自旋等待一段时间,避免线程挂起和恢复带来的性能损失。如果自旋等待的时间较长,说明锁的竞争比较激烈,当前线程应该及时释放CPU资源,让其他线程有机会执行。
自适应的自旋实现在ObjectSynchronizer::FastHashCode(函数中。该函数会根据轻量级锁自旋等待的情况,调整自旋等待的时间。
5.自旋的实现
在OpenJDK 8的源码中,synchronized的升级过程中涉及到了多次自旋操作,其中包括:
第一次自旋:在尝试获取轻量级锁失败后,线程会进行自旋,使用CAS操作去尝试获取锁。这里的
自旋并没有使用while循环,而是使用了C++的inline函数,如ObjectSynchronizer::FastLock0)。
第二次自旋:在尝试获取重量级锁失败后,线程会进行自旋,等待拥有锁的线程释放锁。这里的自
旋同样使用了C++的inline函数,如ObjectSynchronizer…:FastUnlock()。
自适应自旋:在尝试获取轻量级锁时,线程会进行自旋,等待拥有锁的线程释放锁。但这里的自旋不是固定次数的,而是根据前一次自旋的时间和成功获取锁的概率进行自适应调整。这里的自旋实现在C++的Thread.inline.hpp中,如Thread::SpinPause(。
需要注意的是,虽然这些自旋操作并没有使用while循环实现,但其本质上都是在不断尝试获取锁或等待锁的过程中循环执行的。这些循环操作使用的是各种内建函数、指令集和C++的语法特性实现,能够更高效地执行自旋操作,从而提升锁的性能。