Bootstrap

synchronized原理

Synchronized能够实现原子性和可见性:在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。

  • synchronized是一种互斥锁,一次只能允许一个线程进入被锁住的代码块
  • synchronized是Java的一个关键字,它能够将代码块/方法锁起来
  • 如果synchronized修饰的是实例方法,对应的锁则是对象实例
  • 如果synchronized修饰的是静态方法,对应的锁则是当前类的Class实例
  • 如果synchronized修饰的是代码块,对应的锁则是传入synchronized的对象实例
    在这里插入图片描述

synchronized的原理

  • 通过反编译可以发现:当修饰方法时,编译器会生成 ACC_SYNCHRONIZED 关键字用来标识。
  • 当修饰代码块时,会依赖monitorenter和monitorexit指令
  • 但前面已经说了,无论synchronized修饰的是方法还是代码块,对应的锁都是一个实例(对象)
  • 在内存中,对象一般由三部分组成,分别是对象头、对象实际数据和对齐填充。重点在于对象头,对象头又由几部分组成,但我们重点关注对象头Mark Word的信息。Mark Word会记录对象关于锁的信息候选者。monitor对象中存储着当前持有锁的线程以及等待锁的线程队列候选者。
    在这里插入图片描述

锁升级

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,Lock Record 会把Mark Word的信息拷贝进去,且有个Owner指针指向加锁的对象。线程执行到同步代码时,则用CAS试图将Mark Word的指向到线程栈帧的Lock Record

    • CAS修改成功,则获取得到轻量级锁
    • CAS修改失败,则自旋(重试),自旋一定次数后,则升级为重量级锁

简单总结一下:synchronized锁原来只有重量级锁,依赖操作系统的mutex指令,需要用户态和内核态切换,性能损耗十分明显。重量级锁用到monitor对象,而偏向锁则在Mark Word记录线程ID进行比对,轻量级锁则是拷贝Mark Word到Lock Record,用CAS+自旋的方式获取。
在这里插入图片描述
引入了偏向锁和轻量级锁,就是为了在不同的使用场景使用不同的锁,进而提高效率。锁只有升级,没有降级
1)只有一个线程进入临界区,偏向锁
2)多个线程交替进入临界区,轻量级锁
3)多线程同时进入临界区,重量级锁

;