01 锁的膨胀过程
锁膨胀过程就是无锁 → 偏向锁 → 轻量级锁 → 重量级锁的一个过程。这个过程是随着多线程对锁的竞争越来越激烈,锁逐渐升级膨胀的过程。
(1) 一个锁对象刚开始创建时,没有任何线程来访问它,此时线程状态为无锁状态。Mark word(锁标志位-01 是否偏向-0)
(2) 线程A来访问对象锁,它会偏向线程A。线程A检查Mark word(锁标志位-01 是否偏向-0)为无锁状态。此时,有线程访问锁了,无锁升级为偏向锁,Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID)
(3) 线程A执行完同步代码块时,不会主动释放偏向锁,而是等待其他线程来竞争才会释放锁。Mark word不变(锁标志位-01,是否偏向-1,线程ID-线程A的ID)
(4) 当线程A再次获取这个对象锁时,检查Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID),偏向锁且偏向线程A,可直接执行同步代码。这样偏向锁保证了总是同一个线程多次获取锁的情况下,每次只需要检查标志位就行,效率很高。
(5) 当线程A执行完同步块后,线程B获取这个对象锁检查Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID),偏向锁且偏向线程A。有不同的线程获取锁对象,偏向锁升级为轻量级锁,并由线程B获取该锁。
(6) 当线程A正在执行同步块时,也就是正持有偏向锁时,线程B获取来这个对象锁。检查Mark word(锁标志位-01,是否偏向-1,线程ID-线程A的ID),偏向锁且偏向线程A。
线程A撤销偏向锁:
等到全局安全点执行撤销偏向锁,暂停持有偏向锁的线程A并检查程A的状态;如果线程A不处于活动状态或已经退出同步代码块,则将对象锁设置为无锁状态,然后再升级为轻量级锁。由线程B获取轻量级锁。如果线程A还在执行同步代码块,也就是线程A还需要这个对象锁,则偏向锁膨胀为轻量级锁。
线程A膨胀为轻量级锁过程:
在升级为轻量级锁前,持有偏向锁的线程(线程A)是暂停的线程A栈帧中创建一个名为锁记录的空间(Lock Record),锁对象头中的Mark Word拷贝到线程A的锁记录中Mark Word的锁标志位变为00,指向锁记录的指针指向线程A的锁记录地址,Mark word(锁标志位-00,其他位-线程A锁记录的指针)当原持有偏向锁的线程(线程A)获取轻量级锁后,JVM唤醒线程A,线程A执行同步代码块
线程A撤销偏向锁:
等到全局安全点执行撤销偏向锁,暂停持有偏向锁的线程A并检查程A的状态;如果线程A不处于活动状态或已经退出同步代码块,则将对象锁设置为无锁状态,然后再升级为轻量级锁。由线程B获取轻量级锁。如果线程A还在执行同步代码块,也就是线程A还需要这个对象锁,则偏向锁膨胀为轻量级锁。
线程A膨胀为轻量级锁过程:
在升级为轻量级锁前,持有偏向锁的线程(线程A)是暂停的线程A栈帧中创建一个名为锁记录的空间(Lock Record),锁对象头中的Mark Word拷贝到线程A的锁记录中Mark Word的锁标志位变为00,指向锁记录的指针指向线程A的锁记录地址,Mark word(锁标志位-00,其他位-线程A锁记录的指针)当原持有偏向锁的线程(线程A)获取轻量级锁后,JVM唤醒线程A,线程A执行同步代码块
(7) 线程A持有轻量级锁,线程A执行完同步块代码后,一直没有线程来竞争对象锁,正常释放轻量级锁。释放轻量级锁操作:CAS操作将线程A的锁记录(Lock Record)中的Mark Word替换回锁对象头中。
(8) 线程A持有轻量级锁,执行同步块代码过程中,线程B来竞争对象锁。Mark word(锁标志位-00,其他位-线程A锁记录的指针)
线程B会先在栈帧中建立锁记录,存储锁对象目前的Mark Word的拷贝线程B通过CAS操作尝试将锁对象的Mark Word的指针指向线程B的Lock Record,如果成功,说明线程A刚刚释放锁,线程B竞争到锁,则执行同步代码块。因为线程A一直持有锁,大部分情况下CAS是会失败的。CAS失败之后,线程B尝试使用自旋的方式来等待持有轻量级锁的线程释放锁。线程B不会一直自旋下去,如果自旋了一定次数后还是失败,线程B会被阻塞,等待释放锁后唤醒。此时轻量级锁就会膨胀为重量级锁。Mark word(锁标志位-10,其他位-重量级锁monitor的指针)线程A执行完同步块代码之后,执行释放锁操作,CAS 操作将线程A的锁记录(Lock Record)中的Mark Word 替换回锁对象对象头中,因为对象头中已经不是原来的轻量级锁的指针了,而是重量级锁的指针,所以CAS操作会失败。释放轻量级锁CAS操作替换失败之后,需要在释放锁的同时需要唤醒被挂起的线程B。线程B被唤醒,获取重量级锁monitor