热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java互斥语义的实现

锁对象头(ObjectHeader)HotSpot虚拟机的对象头包括两部分信息:MarkWord(标记字段)和KlassPointer(类型指针)Mark

对象头(Object Header)

HotSpot 虚拟机的对象头包括两部分信息:Mark Word(标记字段)和 Klass Pointer(类型指针)

 

  1. Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode )、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 、偏向时间戳等等。JVM 对象头一般占用两个机器码,在 32-bit JVM 上占用 64bit , 在 64-bit JVM 上占用 128bit 16 bytes (暂不考虑开启压缩指针的场景)。另外,如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 Java 对象的元数据信息确定 Java 对象的大小,但是从数组的元数据中无法确定数组的大小。

    存储内容

    标志位

    状态

    对象哈希码、对象分代年龄

    01

    未锁定

    指向锁记录的指针

    00

    轻量级锁定

    指向重量级锁的指针

    10

    膨胀(重量级锁定)

    空,不需要记录信息

    11

    GC标记

    偏向线程ID、偏向时间戳、对象分代年龄

    01

    可偏向

    2.Klass Pointer,即是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

     

    偏向锁

    Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了"偏向锁""轻量级锁",在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

    HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

    1)偏向锁的撤销

    偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。图2-1中的线程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程。

    2)关闭偏向锁

        偏向锁在Java 6Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-

    UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

     

    轻量级锁 BasicObjectLock

    1)轻量级锁加锁

    线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

    2)轻量级锁解锁

    轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。图2-2是两个线程同时争夺锁,导致锁膨胀的流程图。

     

    把要加锁对象Mark Word拷贝到栈帧Lock Record中,并把对象Mark Word执行Lock Record。

    普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法。

    如果对象没有被锁定

    将对象头的Mark指针保存到锁对象中

    将对象头设置为指向锁的指针(在线程栈空间中)

    1. lock->set_displaced_header(mark);
    2. if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
    3. TEVENT (slow_enter: release stacklock) ;
    4. return ;
    5. }

    如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁)

    在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗

    在竞争激烈时,轻量级锁会多做很多额外操作,导致性能下降。

     

    3.锁的优缺点对比

     

    自旋锁

    Jdk1.6以上的自旋锁根据虚拟机监控信息自适应选择自旋时间长度和是否自旋。当竞争存在时,线程可以先等待一下,这时锁持有线程可能已经释放锁,从而避免互斥同步挂起和恢复线程的消耗。这个等待是CPU执行一些操作,可能自旋消耗大于线程挂起恢复。

    注:内置于JVM中的获取锁的优化方法和获取锁的步骤

    偏向锁可用会先尝试偏向锁

    轻量级锁可用会先尝试轻量级锁

    以上都失败,尝试自旋锁

    再失败,尝试普通锁,使用OS互斥量在操作系统层挂起

     

    [1]    本节一些内容参考了HotSpot源码、对象头源码markOop.hpp、偏向锁源码

    biasedLocking.cpp,以及其他源码ObjectMonitor.cppBasicLock.cpp

     

    锁优化

    减小锁粒度

    将大对象,拆成小对象,大大增加并行度,降低锁竞争

    偏向锁,轻量级锁成功率提高,例如:ConcurrentHashMap

    锁分离

    读写分离思想可以延伸,只要操作互不影响,锁就可以分离

    LinkedBlockingQueue

    锁粗化

    一个操作中有个多个加锁片段或者加多个锁。避免多次加锁开销可以把多个锁合并。

    锁消除

    在即时编译器时,如果发现所有数据不可能被共享,则可以对锁消除。

     


推荐阅读
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 深入解析 Synchronized 锁的升级机制及其在并发编程中的应用
    深入解析 Synchronized 锁的升级机制及其在并发编程中的应用 ... [详细]
  • 【妙】bug称它为数组越界的妙用
    1、聊一聊首先跟大家推荐一首非常温柔的歌曲,跑步的常听。本文主要把自己对C语言中柔性数组、零数组等等的理解分享给大家,并聊聊如何构建一种统一化的学习思想 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • Coviam 实习软件工程师的工作体验与成长 ... [详细]
  • Python多线程编程技巧与实战应用详解 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
author-avatar
手机用户2502870367
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有