前言:
在Java并发编程中,synchronized关键字是用于实现线程同步的重要工具。在JVM中,synchronized的底层实现涉及到了偏向锁、轻量级锁和重量级锁这三种锁状态,以及锁升级过程。在之前的文章中介绍到过,这篇文章详细对锁升级进行说明。
偏向锁
偏向锁是Java 6中引入的一种优化手段,它的核心思想是:如果一个线程获得了锁,并且没有释放,那么下次当这个线程再次尝试获得这个锁的时候,不需要做任何同步操作,不需要进行CAS操作或者阻塞,可以直接获得这个锁
。偏向锁主要是为了避免无竞争情况下的同步原语,进一步提高程序的运行性能。
如何获取偏向锁
当一个线程首次访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储偏向的线程ID。
在后续操作中,如果这个线程再次尝试获得这个锁,JVM只需检查对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果是,就表示线程已经获得了锁,可以继续执行同步代码块。
如何撤销偏向锁
当一个线程释放偏向锁时,JVM并不会立即清除对象头中的偏向线程ID,而是将其设置为一个特殊值,表示该锁已经被释放。这样,当其他线程尝试获取这个锁时,JVM会检测到这个特殊值,从而知道该锁已经被释放,可以重新进行偏向锁的获取操作。
轻量级锁
轻量级锁是为了减少获得锁和释放锁所带来的性能消耗而引入的
。在锁竞争不激烈的情况下,轻量级锁可以提高程序的性能。它的实现主要依赖于自旋和CAS操作。
如何轻量级锁
当线程尝试获取一个被其他线程持有的轻量级锁时,它会进入自旋状态,尝试通过CAS操作获取锁。如果自旋等待超过预定的次数仍然没有成功获得锁,那么该线程将会被挂起,转换为重量级锁。在Java 6之后,对于非偏向锁的同步块,在第一次被访问时也会尝试使用轻量级锁。
如何轻量级锁
轻量级锁的释放过程相对简单。当持有锁的线程释放锁时,只需将对象头中的Mark Word设置为无锁状态即可。这样,其他线程就可以尝试获取这个轻量级锁了。
重量级锁
重量级锁是传统的同步锁实现方式,它在JVM中是通过对象监视器Monitor来实现的。当一个线程尝试获取一个被其他线程持有的重量级锁时,它会被阻塞并放入锁的等待队列中。
当持有锁的线程释放锁时,等待队列中的一个线程会被唤醒并获得锁。这种实现方式虽然保证了线程安全,但是在竞争激烈的情况下会导致大量的线程切换和上下文切换,从而带来较大的性能开销。
锁升级过程
接下来详细介绍一下锁升级的过程,这块也是面试中的重点!
在JVM中,synchronized的底层实现涉及到了偏向锁、轻量级锁和重量级锁这三种锁状态的转换过程,即锁升级过程。
当一个线程首次访问同步块并获得锁时,该锁的状态为偏向锁。
在后续的操作中,如果该线程再次尝试获得这个锁,JVM会检查该锁的状态并进行相应的处理。如果该锁的状态为偏向锁且偏向的线程ID与当前线程的ID相同,则表示该线程已经获得了锁,可以继续执行同步代码块;如果该锁的状态为偏向锁但偏向的线程ID与当前线程的ID不同,则表示该锁已经被其他线程持有,需要进行竞争。
此时,JVM会将该锁的状态升级为轻量级锁或重量级锁,具体取决于竞争的激烈程度。如果竞争不激烈,JVM会尝试使用CAS操作获取轻量级锁;如果竞争激烈或自旋等待超过预定的次数仍然没有成功获得轻量级锁,则将该锁的状态升级为重量级锁并阻塞当前线程放入等待队列中等待唤醒。
当持有锁的线程释放锁时也会根据当前锁的状态进行相应的处理。如果当前锁的状态为重量级则唤醒等待队列中的一个线程并获得锁;如果当前锁的状态为轻量级或偏向则只需将对象头中的Mark Word设置为无锁状态即可。
同时也祝愿各位小伙伴儿在面试的过程中这块知识点答得都对,看完全会!
文章到这里就先结束了,感兴趣的可以订阅专栏哈,后续会继续分享相关的知识点。