03. synchronized

3y 2021-08-19 11:56:23
Categories: Tags:

面试官今天我们来聊聊synchronized吧?

候选者:嗯嗯嗯,没问题

候选者:synchronized是一种互斥锁,一次只能允许一个线程进入被锁住的代码块

候选者:synchronized是Java的一个关键字,它能够将代码块/方法锁起来

候选者:如果synchronized修饰的是实例方法,对应的锁则是对象实例

候选者:如果synchronized修饰的是静态方法,对应的锁则是当前类的Class实例

候选者:如果synchronized修饰的是代码块,对应的锁则是传入synchronized的对象实例

面试官:嗯,要不你来讲讲synchronized的原理呗?

候选者:通过反编译可以发现

候选者:当修饰方法时,编译器会生成 ACC_SYNCHRONIZED 关键字用来标识

候选者:当修饰代码块时,会依赖monitorenter和monitorexit指令

候选者:但前面已经说了,无论synchronized修饰的是方法还是代码块,对应的锁都是一个实例(对象)

候选者:在内存中,对象一般由三部分组成,分别是对象头、对象实际数据和对齐填充

候选者:重点在于对象头,对象头又由几部分组成,但我们重点关注对象头Mark Word的信息就好了

候选者:Mark Word会记录对象关于锁的信息

候选者:又因为每个对象都会有一个与之对应的monitor对象,monitor对象中存储着当前持有锁的线程以及等待锁的线程队列

候选者:了解Mark Word和monitor对象是理解 synchronized 原理的前提

面试官:嗯,听说synchronized锁在 JDK 1.6 之后做了很多的优化,这块你了解多少呢?

候选者:其实是这样的,在JDK 1.6之前是重量级锁,线程进入同步代码块/方法 时

候选者:monitor对象就会把当前进入线程的Id进行存储,设置Mark Word的monitor对象地址,并把阻塞的线程存储到monitor的等待线程队列中

候选者:它加锁是依赖底层操作系统的 mutex 相关指令实现,所以会有用户态和内核态之间的切换,性能损耗十分明显

候选者:而JDK1.6 以后引入偏向锁和轻量级锁在JVM层面实现加锁的逻辑,不依赖底层操作系统,就没有切换的消耗

候选者:所以,Mark Word对锁的状态记录一共有4种:无锁、偏向锁、轻量级锁和重量级锁

面试官:简单来说说偏向锁、轻量级锁和重量级锁吧

候选者:嗯,没问题

候选者:偏向锁指的就是JVM会认为只有某个线程才会执行同步代码(没有竞争的环境)

候选者:所以在Mark Word会直接记录线程ID,只要线程来执行代码了,会比对线程ID是否相等,相等则当前线程能直接获取得到锁,执行同步代码

候选者:如果不相等,则用CAS来尝试修改当前的线程ID,如果CAS修改成功,那还是能获取得到锁,执行同步代码

候选者:如果CAS失败了,说明有竞争环境,此时会对偏向锁撤销,升级为轻量级锁。

候选者:在轻量级锁状态下,当前线程会在栈帧下创建Lock Record,LockRecord 会把Mark Word的信息拷贝进去,且有个Owner指针指向加锁的对象

候选者:线程执行到同步代码时,则用CAS试图将Mark Word的指向到线程栈帧的Lock Record,假设CAS修改成功,则获取得到轻量级锁

候选者:假设修改失败,则自旋(重试),自旋一定次数后,则升级为重量级锁

候选者:简单总结一下

候选者:synchronized锁原来只有重量级锁,依赖操作系统的mutex指令,需要用户态和内核态切换,性能损耗十分明显

候选者:重量级锁用到monitor对象,而偏向锁则在Mark Word记录线程ID进行比对,轻量级锁则是拷贝Mark Word到Lock Record,用CAS+自旋的方式获取。

候选者:引入了偏向锁和轻量级锁,就是为了在不同的使用场景使用不同的锁,进而提高效率。锁只有升级,没有降级

候选者:1)只有一个线程进入临界区,偏向锁

候选者:2)多个线程交替进入临界区,轻量级锁

候选者:3)多线程同时进入临界区,重量级锁

面试官:OK,明白了。

第一时间获取BATJTMD一线互联网大厂最新的面试资料以及内推机会关注公众号「对线面试官