偏向锁、轻量级锁、重量级锁
因为早期Java版本的synchronized底层实现采取的是操作系统的互斥量,线程阻塞和唤醒的代价很大,性能较低,因此Java尝试在多线程竞争不那么激烈的情况下,降低锁的开销。
1、偏向锁
偏向锁适用于只有一个线程进入同步代码的情况
偏向锁会偏向于获得偏向锁的线程,它会在对象头的markword存储当前获取偏向锁的线程ID,当该线程下次获取锁的时候无需额外的操作只需要判断一下当前线程是否指向自己,如果指向自己则成功获得锁,否则进行偏向锁的撤销
(偏向锁的撤销会产生两种结果:1、另一个线程成功获得偏向锁.2、偏向锁升级为轻量级锁)
偏向锁加锁过程
如果当前对象是无锁可偏向状态,那么会采用CAS算法将锁对象的markword中写入自己的线程ID,如果写入成功,则成功获取锁,否则发生了竞争,进行偏向锁的撤销流程。
偏向锁的撤销
首先jvm会暂停所有线程的执行,然后检查当前对象头中记录的线程ID所指向的线程是否存活、或者是否执行完同步代码,如果执行完了,说明此时偏向锁处于无锁状态,将会重偏向给发起偏向锁撤销的那个线程。如果记录的线程ID仍然存活着,那么将偏向锁升级为轻量级锁。
2、轻量级锁
轻量级锁适用于线程交替进入同步代码区的情况
轻量级锁基本原理是采用CAS+自旋的方式避免线程阻塞和唤醒的开销。
轻量级锁的加锁过程
首先如果发现对象处于无锁状态,那么会在当前栈帧中开辟一块空间,保存对象markword的副本,然后通过CAS算法将对象头的Markword设置为指向该副本的指针,如果设置成功,则说明成功获取锁,如果获取失败,则自旋等待获取锁(当自旋多次依旧没有获取到锁时。会将轻量级锁升级为重量级锁(设置对象的锁标记为重量级锁))。
轻量级锁的释放
使用CAS算法将栈帧中保存的markword副本放回对象头中,如果成功则加锁失败,如果失败了,那么说明在持有锁的过程中锁被升级为了重量级锁,因此会释放锁,并且唤醒阻塞等待的线程。
3、重量级锁
重量级锁适用于线程竞争激烈的场景
采用操作系统互斥量实现,当发现对象无锁时,会获取锁。如果获取锁失败或者对象本身就处于锁定状态,会进入阻塞状态,等待线程唤醒。