在Java中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁,这种锁升级却不能降级的策略,目的是为了提高锁和释放锁的效率。

偏向锁:

偏向锁的设计初衷:

锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁的概念

偏向锁获取锁流程如下:

1.当一个线程访问同步块并获取锁时,会对对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁

2.如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁);

3.如果没有设置,则使用CAS竞争锁

4.如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程

偏向锁升级为轻量锁

对于只有一个线程访问的同步资源场景,锁的竞争不是很激烈,这时候使用偏向锁是一种很好的选择,因为连续使用多次极有可能是同一线程请求相同的锁。但是在锁竞争比较激烈的场景,最有可能的情况是每次不同的线程来请求相同的锁,这样的话偏向锁就会失效,倒不如不开启这种模式,幸运的是Java虚拟机提供了参数可以让我们有选择的设置是否开启偏向锁。如果偏向锁失败,虚拟机并不会立即挂起线程,而是使用轻量级锁进行操作。

轻量级锁

如果偏向锁失败,虚拟机并不会立即挂起线程,而是使用轻量级锁进行操作。轻量级锁只是简单的将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。如果线程获得轻量级锁成功,则可以顺利进入临界区,如果轻量级锁加载失败,则表示其他线程抢先夺到锁,那么当前线程的轻量级锁就会膨胀为重量级锁。

自旋锁

轻量级锁会膨胀为重量级锁后,虚拟机为了避免线程真实的在操作系统层面挂起,虚拟机还会再做最后的努力:自旋锁。由于当前线程暂时无法获得锁,但是什么时候获得锁是一个未知数,也许在几个CPU时钟周期之后,就可以获得锁。如果是这样的话,直接把线程挂起肯定是一种得不偿失的选择,因此系统会再进行一次努力:他会假设在不久的将来,限额和从那个可以得到这把锁,因此虚拟机会让当前线程做几个空循环(这也就是自旋锁的意义),若经过几个空循环可以获取到锁则进入临界区,如果还是获取不到则系统会真正的挂起线程。自旋锁无法预知到底会空循环几个时钟周期,并且会很消耗CPU,为了避免这种无用的自旋操作,一旦锁升级为重量锁,就不会再恢复到轻量级锁。

三种锁的优缺点对比

java 和对象锁 静态锁 java锁状态_JVM