Java对象头

JVM中对象头的结构有以下两种(以32位JVM为例):
普通对象的对象头结构

数组对象的对象头结构

其中Mark Word结构

64位虚拟机 Mark Word的结构

Mark Word这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄、锁状态标记位等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。
lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向时间戳。
ptr_to_lock_record:指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:指向管程Monitor的指针。

Monitor

Monitor被翻译为监视器或管程。
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级锁)之后,该对象头中的Mark Word就被设置为指向Monitor对象的指针 。

Owner:用于记录当前Monitor的所属线程
EntryList:是一个链表结构,用于记录阻塞在当前锁对象上的线程
当对象锁发生锁竞争时,在同一时刻只有一个线程能够获取到锁,其他线程会进入阻塞(BLOCKED)状态,此时这些被阻塞的线程就会进入EntryList中等待锁持有者释放锁后被唤醒,再次参与锁竞争(非公平)。当Thread-2持有锁时,Thread-3、Thread-4等均无法获取锁从而进入阻塞队列,等待Thread-2执行完同步代码块之后通知阻塞队列中等待的线程重新竞争锁,竞争成功的线程成为锁拥有者,失败的线程继续在阻塞队列中阻塞。
WaitSet:用于记录获取锁之后进入Waiting状态的线程
当对象获取到锁之后,由于某些资源并未准备完成,需要等待其他线程去准备资源,此时线程会通过wait()/notify()等方法进入等待/通知模式,在这种情况下线程释放锁之后会进入WaitSet,当其他线程准备好资源之后会通知WaitSet中等待的线程,WaitSet中的线程会进入到EntryList中,重新参与锁竞争。
注意:
● synchronized 必须是进入同一个对象的 monitor 才有上述的效果
● 不加 synchronized 的对象不会关联监视器,不遵从以上规则

synchronized的作用

synchronized 通过当前线程持有对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程无法拥有访问权限,保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,从而保证线程安全。synchronized 可以保证线程的可见性,synchronized 属于隐式锁,锁的持有与释放都是隐式的,我们无需干预。synchronized最主要的三种应用方式:
● 修饰实例方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁
● 修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
● 修饰代码块:指定加锁对象,进入同步代码库前要获得给定对象的锁

synchronized的可重入性

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

JVM对synchronized锁的优化

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,只能从低到高升级,不会出现锁的降级。重量级锁基于从操作系统的互斥量实现的,而偏向锁与轻量级锁不同,他们是通过CAS并配合Mark Word一起实现的。

轻量级锁

如果一个对象虽然有多个线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
假如有两个方法同步块,利用同一个对象加锁

static final Object obj = new Object();
public static void method1() {synchronized( obj ) {// 同步块 Amethod2();}
}
public static void method2() {synchronized( obj ) {// 同步块 B}
}
  1. 在栈中创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word 。
  2. 让锁记录中Object reference指向锁对象,并尝试用cas操作替换Object的Mark Word,将Mark Word的值存入Lock Record锁记录 。
  3. 如果cas操作替换成功,对象头Mark Word中存储了锁记录地址和锁状态 00 ,表示由该线程给对象加锁,这时图示如下

    如果cas操作失败,有两种情况:
    ● 如果是其它线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程升级为重量级锁
    ● 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数,并指向锁对象
  4. 当退出synchronized代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
  5. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
    ● 成功,则解锁成功
    ● 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
适用于连续多次都是同一个线程申请相同的锁的场景。偏向锁只有初始化的时候需要一次CAS操作,但如果出现其他线程竞争锁资源,那么偏向锁就会被撤销,并升级为轻量级锁。

偏向状态

● 一个对象创建时: 如果开启了偏向锁(默认开启),那么对象创建后,MarkWord值的最后3位为101,这时它的 thread、epoch、age 都为 0
● 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 - XX:BiasedLockingStartupDelay=0 来禁用延迟
● 如果没有开启偏向锁,那么对象创建后,markword 值的最后3位为 001,这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值

偏向锁的废除

在 JDK6 中引入的偏向锁能够减少竞争锁定的开销,使得 JVM 的性能得到了显著改善,但是 JDK15 却将决定将偏向锁禁用,并在以后删除它,这是为什么呢?主要有以下几个原因:
● 为了支持偏向锁使得代码复杂度大幅度提升,并且对 HotSpot 的其他组件产生了影响,这种复杂性已成为理解代码的障碍,也阻碍了对同步系统进行重构
● 在更高的 JDK 版本中针对多线程场景推出了性能更高的并发数据结构,所以过去看到的性能提升,在现在看来已经不那么明显了。
● 围绕线程池队列和工作线程构建的应用程序,性能通常在禁用偏向锁的情况下变得更好。

锁自旋

在轻量级锁升级成为重量级锁之前,虚拟机会让当前想要获取锁的线程做几个空循环,在经过若干次循环后,如果得到锁(即这个时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
自旋重试成功的情况

自旋重试失败的情况

自旋会占用 CPU 时间,单核CPU自旋就是浪费,多核 CPU 自旋才能发挥优势。
在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
Java 7 之后不能控制是否开启自旋功能

锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

  1. 当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁
  2. 这时Thread-1加轻量级锁失败,进入锁膨胀流程
    ● 即为Object对象申请Monitor锁,让Object锁对象的Mark Word指向Monitor对象
    ● Monitor的Owner记录Thread-0线程
    ● 然后自己(Thread-1线程)进入Monitor的EntryList阻塞队列中
  3. 当Thread-0退出同步块解锁时,使用cas操作将Mark Word的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor地址找到Monitor对象,设置Owner为 null,唤醒EntryList中BLOCKED线程

重量级锁

适用于多个线程同时执行同步代码块的场景,且锁竞争时间长。在这个状态下,未抢到锁的线程都会进入到 Monitor 中并阻塞在EntryList中。

锁消除

消除锁属于编译器对锁的优化,JIT 编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译)会使用逃逸分析技术,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间。

锁粗化

JIT 编译器动态编译时,如果发现几个相邻的同步块使用的是同一个锁实例,那么 JIT 编译器将会把这几个同步块合并为一个大的同步块,从而避免一个线程“反复申请、释放同一个锁“所带来的性能开销。

synchronized同步锁原理详解相关推荐

  1. mysql悲观锁会有脏数据吗_mysql悲观锁原理详解

    mysql中的锁概念 mysql已经成为大家日常数据存储的最常用平台,但随着业务量和访问量的上涨,会出现并发访问等场景,如果处理不好并发问题的话会带来严重困扰.下面介绍一下如何通过mysql的悲观锁来 ...

  2. java代码轻量级锁_Java轻量级锁原理详解(Lightweight Locking)

    转自http://www.cnblogs.com/redcreen/archive/2011/03/29/1998801.html 大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的 ...

  3. Java 轻量级锁原理详解(Lightweight Locking)

    2019独角兽企业重金招聘Python工程师标准>>> 大家知道,Java的多线程安全是基于Lock机制实现的,而Lock的性能往往不如人意. 原因是,monitorenter与mo ...

  4. Spring - JUC 公平锁和非公平锁原理详解

    一.简介 Java语言中有许多原生线程安全的数据结构,比如ArrayBlockingQueue.CopyOnWriteArrayList.LinkedBlockingQueue,它们线程安全的实现方式 ...

  5. 【IoT】硬件产品设计:指纹锁临时密码开锁原理详解

    原理: 手机App跟门锁在出厂的时候就进行过一对一匹配算法. 此算法原理和银行保险机制相同,无需通过云端发出指令,生成一次性密码时所以也无需搭配网关. 临时密码的使用时间和次数是两个关键因素,按这两个 ...

  6. Java多线程系列(十一):ReentrantReadWriteLock的实现原理与锁获取详解

    我们继续Java多线程与并发系列之旅,之前我们分享了Synchronized 和 ReentrantLock 都是独占锁,即在同一时刻只有一个线程获取到锁. 然而在有些业务场景中,我们大多在读取数据, ...

  7. AQS抽象队列同步器原理详解

    系列文章目录 第一节 synchronized关键字详解-偏向锁.轻量级锁.偏向锁.重量级锁.自旋.锁粗化.锁消除 AQS抽象队列同步器原理详解 系列文章目录 前言 一.AQS特性 二.AQS原理 1 ...

  8. Java Synchronized 重量级锁原理深入剖析上(互斥篇)

    前言 线程并发系列文章: Java 线程基础 Java 线程状态 Java "优雅"地中断线程-实践篇 Java "优雅"地中断线程-原理篇 真正理解Java ...

  9. sync.Map低层工作原理详解

    sync.Map低层工作原理详解 目录 为什么需要sync.Map?适合什么场景? sync.Map内部实现基本原理及结构体分析 sync.Map低层工作原理 1. 为什么需要sync.Map?适合什 ...

最新文章

  1. 解决mysqlslap执行命令报错(BEGIN failed--compilation aborted at //bin/mysqlslap line 2098)usr...
  2. 初识Mysql(part5)--我需要知道的11条Mysql语句之过滤
  3. 有关sed命令的用法
  4. .NET跨平台实践:Linux .Net Core自宿主应用程序瘦身记
  5. Collapse Hierarchy(折叠继承体系)
  6. python sql查询返回记录_干货!Python与MySQL数据库的交互实战
  7. 专注企业市场 或是网盘危机的有效出路
  8. nginx中给目录增加密码保护实现程序
  9. android 手机内存uri_Android消息机制Handler原理解析
  10. ubuntu装机必备+主题美化
  11. 百度云安装WordPress,提示数据库连接错误!
  12. 电子系统综合设计作业笔记
  13. Ringtone 循环播放铃声
  14. Windows10系统安装详细教程
  15. 目标检测中region proposal的作用?
  16. 打造云原生大型分布式监控系统
  17. flink sql 知其所以然(二)| 自定义 redis 数据维表(附源码)
  18. 星起航:抖音小店线上货源渠道
  19. 2020笔记本性价比之王_2020十大笔记本电脑性价比排行(最新笔记本电脑推荐)...
  20. 第十章 PL/SQL对象类型

热门文章

  1. 【pg Postgres】 Postgres解决Permission denied for relation
  2. VQLS:变分量子算法解线性方程组
  3. 腾讯云windos服务器如何迁移?
  4. 圆周率为什么会等于4?
  5. 安卓系统源码编译系列(一)——下载安卓系统源码教程
  6. 关于Google地图路线偏移的问题
  7. php中网页生成图片的方式,类似长微博图片生成器
  8. 机器学习“调音师”:如何及何时重新调校ML
  9. Android照片墙加强版,使用ViewPager实现画廊效果
  10. 联想笔记本更换固态硬盘和重装系统