java 并发编程 - synchronized(5)

141 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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等信息,将它存放到monitorheader字段中(不能丢失对象信息),并将monitor的owner设置为自己。

重量级锁

重量级锁的对象头中30bit会指向对应的ObjectMonitor,并且锁标志位为10

monitor结构

流程

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线程阻塞响应比较慢追求吞吐量,同步内容耗费的时间长