synchronized原理分析笔记

使用场景

  • 使用场景分为两种,一种是用于类,一种是用于对象;
  • 类: 修饰静态方法、同步块指向类;修饰方法指向当前类,同步块指向指定的类
  • 对象: 修饰非静态方法、同步块指向对象;修饰方法指向当前实例,同步块指向指定的对象
  • 每个对象和类都有自己独立的监听器Monitor,jvm用Monitor实现Lock;比如HashTable.class 有一个Monitor,每个new HashTable() 也有自己Monitor;
  • 监听器Monitor同一时间只能有一个线程持有,也就是说一个对象的某个同步方法或同步块被任意线程访问时,改对象的所有同步方法和同步块都会阻塞

锁的升级(膨胀)

  • jdk6之后引入了偏向锁、轻量级锁,就有了4种状态(无锁、偏向锁、轻量级锁、重量级锁);
  • 锁在被持有时才会膨胀改变对象头中状态,一旦释放后会状态重置
  • 锁状态存储在对象头 (下图是32位系统的【对象头结构】)
    对象头结构
  • 无锁(禁用偏向)

    对象头内容:偏向标志(0)、标志位(01)、hashCode(生成过hashCode的对象有值);
    未开启偏向锁时创建的对象、生成过hashCode的对象、数组对象都是无锁(禁用偏向)状态​
    jvm默认开启偏向锁,但是延迟开启默认4秒,所以默认前4秒创建的对象不可偏向;可以通过 -XX:BiasedLockingStartupDelay=0改为零延时(ms)
    禁用偏向的对象锁升级时直接到轻量级锁
    调用Object.hashCode() 或System.identityHashCode()生成hashCode; PS:第一次调用时才会生成,后面再调用直接从对象中获取

  • 偏向锁

    对象头内容:偏向标志(1)、标志位(01)、偏向线程ID、时间戳;初始未偏向任何线程时不记录任何线程ID;
    当某个线程尝试执行同步代码时,如果对象记录的线程ID与之相同直接执行,否则使用CAS修改线程ID,修改失败则升级为轻量级锁
    偏向锁状态下,对象生成hashCode,会直接升级为重量级锁

  • 轻量级锁

    对象头内容:偏向标志(0)、标志位(00)、Lock Record的指针;
    当某个线程尝试执行同步代码时,如果为无锁状态,创建Lock Record并copy mark word;然后用CAS更新Lock Record的指针,更新成功直接执行,否则判断是否是指向当前线程;如果是直接执行,否则自旋CAS更新Lock Record的指针
    自旋到达指定次数或者再有一个线程来竞争锁就会升级到重量级锁
    锁释放使用CAS将copy的mark word写回,并改写偏向标志、标志位

  • 重量级锁

    对象头内容:偏向标志(0)、标志位(10)、Monitor的指针;
    膨胀至重量级锁时,会创建ObjectMonitor对象并copy mark word
    持有锁的线程会阻塞其他线程,通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现
    锁释放也是通过monitor对象,锁释放后会回写mark word

  • 升级过程(懒得画了,从网上找到一个图)
    锁升级过程

对象头的操作

如上【对象头结构】图,对象头分为MarkWord、Class Metadata;Class Metadata存储对象类型的指针(指向元空间的类对象),MarkWord不同状态存储不同内容,附上一张64位系统空间分配明细图(借用)
mark word

  • 查看工具(org.openjdk.jol)

    maven配置
    <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.8</version> </dependency>
    使用 ClassLayout.parseInstance(object).toPrintable() 可以获取到对象头内容,如下图

  • 打印内容 对象头内容

    红框部分是存储值,当前对象是数组,第4行是数组长度,非数组对象只有3行
    第3行是Class Metadata,jvm默认开启指针压缩,所以是32位,如果关闭则是64位;打印出来会多一行 3、4行
    第1、2行是MarkWord内容,位置使用排序是从第2行开始,每行按组从右向左,每8位一组,如下图顺序
    mark word
    第8组的第7、8位是标志位,无锁、偏向锁下第6位是偏向标志;
    第8组的第2~5位是分代年龄,第1位应该是cms使用(开启指针压缩时)
    无锁状态且对象生成了hashCode,第4组的2~8位加上5、6、7组共31位存储hashCode
    偏向锁状态且已偏向某线程,第7组的1~6位加上1~6组共54位存储线程ID,第7组的7、8位存储epoch

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章