一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情
轻量级锁
加锁即释放
在当前线程的帧栈中创建用于存储锁记录(Lock Record) 的空间,将对象头中的markWord(前30bit)复制到Displaced Mark Word中,再CAS将Mark Work替换为指向Lock Record的指针。 相当于给对象头信息换了个地址,放到了Displaced Mard Word中,然后Mark Word指向这个地址。 CAS成功后,说明现在是轻量级锁,将锁标志为变成00.
释放只需要将Displaced Mark word的信息替换回对象的Mark Word中即可
升级条件
在线程A执行过程中,线程B来获取锁,当CAS的时候发现,对象30bit中存的是线程A栈帧的地址而不是hashCode等信息,因此CAS失败。
然后循环10次,如果还是失败,就会升级成重量级锁
升级过程:
线程B会将当前对象的30bit直接指向互斥量的指针(Object的monitor)。
- 对于持有轻量级锁的线程来说,目前还在执行中,但它释放锁,想要将Mark Word信息替换回去时,CAS失败了。那么它就懂了,当前已经是重量级锁了,要走重量级锁的释放锁流程。
- 对于其他竞争的线程来说,放弃自旋,逐个插入到monitor对象的_entryList尾部进入阻塞状态
还需要把轻量级锁中存储的对象信息放入重量级锁中
线程A栈帧中还存放着对象的hashCode等信息,将它存放到monitor的header字段中(不能丢失对象信息),并将monitor的owner设置为自己。
重量级锁
重量级锁的对象头中30bit会指向对应的ObjectMonitor,并且锁标志位为10
流程
1.当多个线程同时访问时,会进入EntryList集合阻塞
2.线程获取到ObjectMonitor对象后,进入 _Owner区域并将onwer变量指向自己,执行monitorenter,计数器+1
3.当调用wait()方法,释放monitor,将owner恢复为null,计数器-1.进入WaitSet等待被唤醒
4.线程执行完毕,也会将owner恢复为null,执行monitorexit
线程通过monitorenter和monitorexit 来获取和释放锁,并且使计数器+1,-1。由于是可重入,因此计数器可能>1,当计数器为0时,代表monitor被释放。
同步方式的成本很高,内核台和用户态切换,线程阻塞等很大影响性能。
wait方法时重量级锁独有的,因此调用该方法会直接升级到重量级锁。
几种锁的优缺点
| 锁 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 偏向锁 | 加锁和解锁不需要额外操作,同步速度非常快 | 多线程的锁竞争会带来额外的消耗(线程id撤销等) | 没有竞争的单线程 |
| 轻量级锁 | 竞争的线程不会阻塞,线程响应高 | 得不到锁的线程会不断自旋消耗CPU | 追求响应速度,同步内容耗费的时间短(不需要CAS很多次) |
| 重量级锁 | 不自旋消耗CPU | 线程阻塞响应比较慢 | 追求吞吐量,同步内容耗费的时间长 |