java 并发编程 - synchronized(3)

121 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

锁升级

为什么JDK1.6要对锁进行优化(重量级锁的缺点)

java线程时映射到操作系统内核原生线程上一对一.因此如果要阻塞或者唤醒线程,就需要操作系统的帮忙,从用户态转换到内核态由操作系统操作,这个状态转换需要花费很多时间。如果对于简单的同步块,执行的时间可能就比状态转换的时间还短。这就是JDK1.6对重量锁优化的初衷。

在JDK1.6之后对sync进行了优化,减少获得和释放锁带来的性能消耗,引入了偏向锁轻量级锁

随着竞争的激烈,锁会逐步升级。无锁-->偏向锁 -->轻量级锁 -->重量级锁

sync锁只能升级,不能降级,读写锁才能降级

1575018455.jpeg

对象头

在锁升级之前,需要先了解对象头。

对象头中里的Mark Word(占据一个字宽,即4个字节-32bit)存储了锁的相关信息(因为任何对象都可以当锁)

image-20220303225223597.png

  • 无锁:25bit存储对象的hashCode,4bit存储分代年龄,1bit记录不是偏向锁-0,2bit记录锁标志-01
  • 偏向锁:23bit存储占据偏向锁的线程ID,1bit记录是偏向锁-1,2bit记录锁标志-01
  • 轻量级锁:30bit指向栈中锁的指针,2bit记录轻量级锁-00
  • 重量级锁:30bit指向重量级锁的指针,2bit记录重量级锁-10
  • GC:30bit为空,2bit记录-11

hashCode

如图,25位存储的是对象的hashCode,但是如果new 了一个对象,但是如果没有隐式或者显式调用父类Object的hashCode方法,此时是不会存储hashCode值

隐式或显式调用hashCode

  • 隐式:创造完后放入hashMap,hashSet等需要获得hashCode的类hash数据结构
  • 显式:在调用的构造方法中实现super.hashCode,自己复写hashCode不会存储

只有在对象头没有存储hashCode的情况下,才会升级成偏向锁

如果存储了hashCode,就会直接进入轻量级锁:JVM只会为对象调用一次hashCode,后续会从对象头的markword中获取,如果升级成偏向锁,那么就会覆盖hashCode,这样会导致hashCode消失。hashCode是随机生成的,那么再次调用就会生成不同的hashCode,产生冲突。