本文 首发于 🍀 永浩转载 请注明 来源

7、【对线面试官】synchronized

今天我们来聊聊synchronized吧?

  1. synchronized是一种互斥锁,一次只能允许一个线程进入被锁住的代码块
  2. synchronized是Java的一个关键字,它能够将代码块/方法锁起来
  3. 如果synchronized修饰的是实例方法,对应的锁则是对象实例
  4. 如果synchronized修饰的是静态方法,对应的锁则是当前类的Class实例
  5. 如果synchronized修饰的是代码块,对应的锁则是传入synchronized的对象实例

嗯,要不你来讲讲synchronized的原理呗?

  1. 通过反编译可以发现
  2. 当修饰方法时,编译器会生成ACC_SYNCHRONIZED关键字用来标识
  3. 当修饰代码块时,会依赖monitorenter和monitorexit指令
  4. 但前面已经说了,无论synchronized修饰的是方法还是代码块,对应的锁都是一个实例(对象)
  5. 在内存中,对象一般由三部分组成,分别是对象头、对象实际数据和对齐填充
  6. 重点在于对象头,对象头又由几部分组成,但我们重点关注对象头Mark Word的信息就好了
  7. Mark Word会记录对象关于锁的信息
  8. 又因为每个对象都会有一个与之对应的monitor对象,monitor对象中存储着当前持有锁的线程以及等待锁的线程队列
  9. 了解Mark Word和monitor对象是理解synchronized原理的前提

嗯,听说synchronized锁在JDK1.6之后做了很多的优化,这块你了解多少呢?

  1. 其实是这样的,在JDK1.6之前是重量级锁,线程进入同步代码块/方法时
  2. monitor对象就会把当前进入线程的Id进行存储,设置Mark Word的monitor对象地址,并把阻塞的线程存储到monitor的等待线程队列中
  3. 它加锁是依赖底层操作系统的mutex相关指令实现,所以会有用户态和内核态之间的切换,性能损耗十分明显
  4. 而JDK1.6以后引入偏向锁和轻量级锁在JVM层面实现加锁的逻辑,不依赖底层操作系统,就没有切换的消耗
  5. 所以,Mark Word对锁的状态记录一共有4种:无锁、偏向锁、轻量级锁和重量级锁

简单来说说偏向锁、轻量级锁和重量级锁吧

  1. 偏向锁指的就是JVM会认为只有某个线程才会执行同步代码(没有竞争的环境)
  2. 所以在Mark Word会直接记录线程ID,只要线程来执行代码了,会比对线程ID是否相等,相等则当前线程能直接获取得到锁,执行同步代码
  3. 如果不相等,则用CAS来尝试修改当前的线程ID,如果CAS修改成功,那还是能获取得到锁,执行同步代码
  4. 如果CAS失败了,说明有竞争环境,此时会对偏向锁撤销,升级为轻量级锁。
  5. 在轻量级锁状态下,当前线程会在栈帧下创建Lock Record,LockRecord会把 Mark Word的信息拷贝进去,且有个Owner指针指向加锁的对象由由
  6. 线程执行到同步代码时,则用CAS试图将Mark Word的指向到线程栈帧的LockRecord,假设CAS修改成功,则获取得到轻量级锁
  7. 假设修改失败,则自旋(重试),自旋一定次数后,则升级为重量级锁
  8. 简单总结一下
    • synchronized锁原来只有重量级锁,依赖操作系统的mutex指令,需要用户态和内核态切换,性能损耗十分明显
    • 重量级锁用到monitor对象而偏向锁则在Mark Word记录线程ID进行比对、轻量级锁则是拷贝Mark Word到Lock Record,用CAS+自旋的方式获取。
  9. 引入了偏向锁和轻量级锁,就是为了在不同的使用场景使用不同的锁,进而提高效率。 锁只有升级,没有降级
    • 只有一个线程进入临界区,偏向锁
    • 多个线程交替进入临界区,轻量级锁
    • 多线程同时进入临界区,重量级锁