Synchronized锁升级过程
Synchronized锁升级过程
首先,每个对象都有一把锁。
对象的结构有三个部分:对象头,实例数据,对其填充字节(保证对象大小为8字节的倍数)
对象头由两部分组成:1.classpointer(指向当前对象在方法区中的类型数据)
2.Markword存储和当前对象运行时状态有关的数据
锁的信息就存在Markword中,在32位JVM下Markword大小为32bit。
Markword最后两位是锁标志位。
无锁:
1. 无竞争情况:无需任何保护,直接给各个线程调用。
2. 存在竞争:用不上锁的方式同步线程,CAS。
偏向锁:
大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,为了降低获取锁的代价,才引入的偏向锁。
加锁:当线程1访问同步块并获取锁时, 会在锁对象的对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁, 只需要比较当前线程的threadID和Java对象头中的threadID是否一致。
升级:如果不一致(线程2竞争锁对象),先查看对象头中记录的线程1是否存活。如果不存活,锁对象重置为无锁状态,其他线程(线程2)可以竞争将其设为偏向锁;如果存活,查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
轻量级锁:
加锁:线程获取轻量级锁时会先把锁对象头MarkWord复制到线程的栈帧中创建的用于存储锁记录的空间,然后尝试使用CAS将对象头中的MarkWord替换为锁记录的地址。如果成功, 当前线程获得锁; 如果失败, 表示其它线程竞争锁, 当前线程便尝试使用自旋来获取锁, 之后再来的线程, 发现是轻量级锁, 就开始进行自旋.
升级:但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
自适应自旋:自旋时间不再固定。当前正在自旋等待的线程,刚刚已经成功获得过锁,但是锁目前被其他线程占用,那么虚拟机就认为这次自旋也很有可能成功,进而允许更长时间的自旋。
Synchronized底层原理:
Synchronized被编译后会生成monitorenter 和 monitorexit 指令。
在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
Monitor是依赖于操作系统的mutex lock实现的,每当挂起或者唤醒1个线程都要切换操作系统内核态。这个操作是比较重量级的,在一些情况下甚至切换时间本身会超出线程执行任务的时间。
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。
部分参考自:https://blog.csdn.net/tongdanping/article/details/79647337