同步和对象锁定

同步 - 同步 - OpenJDK Wiki

Java编程语言的主要优势之一是它内置了对多线程程序的支持。可以锁定在多个线程之间共享的对象,以便同步其访问。Java 提供了用于指定关键代码区域的原语,这些代码区域作用于共享对象,一次只能由一个线程执行。进入该区域的第一个线程锁定共享对象。当第二个线程即将进入同一区域时,它必须等到第一个线程再次解锁对象。

在 Java HotSpot™ VM 中,每个对象前面都有一个类指针和一个标头字。标头字存储身份哈希代码以及用于代际垃圾收集的年龄和标记位,也用于实现薄锁方案 [Agesen99, Bacon98]。下图显示了标题字的布局和不同对象状态的表示形式。

图的右侧说明了标准锁定过程。只要对象被解锁,最后两位的值为 01。当方法在对象上同步时,标头字和指向对象的指针存储在当前堆栈帧内的锁定记录中。然后,VM 尝试通过比较和交换操作在对象的标头字中安装指向锁定记录的指针。如果成功,则当前线程随后拥有锁。由于锁定记录始终在字边界对齐,因此标头字的最后两位为 00,并将对象标识为已锁定。

如果比较和交换操作因对象之前被锁定而失败,则 VM 首先测试标头字是否指向当前线程的方法堆栈。在这种情况下,线程已经拥有对象的锁,并且可以安全地继续执行。对于此类递锁定的对象,锁定记录使用 0 而不是对象的标头字进行初始化。仅当两个不同的线程在同一对象上同时同步时,细锁才必须膨胀到重量级监视器,以管理等待线程。

薄锁比膨胀锁便宜得多,但它们的性能受到影响,因为每个比较和交换操作都必须在多处理器机器上原子地执行,尽管大多数对象仅由一个特定线程锁定和解锁。在Java 6中,这个缺点通过所谓的无存储偏置锁定技术[Russell06]来解决,该技术使用类似于[Kawachiya02]的概念。只有第一个锁采集执行原子比较和交换,以将锁定线程的 ID 安装到标头字中。然后说该对象偏于线程。将来通过同一线程锁定和解锁对象不需要任何原子操作或更新标头字。甚至堆栈上的锁定记录也未初始化,因为它永远不会被检查是否有偏见的对象。

当一个线程在偏向另一个线程的对象上同步时,必须通过使对象看起来好像它已以常规方式锁定来撤销偏差。遍历偏置所有者的堆栈,根据瘦锁方案调整与对象关联的锁记录,并在对象的标头字中安装指向其中最旧的指针。此操作必须挂起所有线程。当访问对象的标识哈希代码时,也会撤销偏差,因为哈希代码位与线程 ID 共享。

显式设计为在多个线程之间共享的对象(如生产者/使用者队列)不适合有偏差锁定。因此,如果过去频繁发生类实例的吊销,则会禁用类的偏置锁定。这称为批量吊销。如果在禁用了偏置锁定的类的实例上调用锁定代码,它将执行标准的瘦锁定。新分配的类实例被标记为无偏置。

一种类似的机制,称为批量重新偏置,优化了类的对象被不同的线程锁定和解锁但从不同时锁定的情况。它使类的所有实例的偏差无效,而不会禁用偏差锁定。类中的纪元值充当指示偏差有效性的时间戳。此值在对象分配时复制到标头字中。然后,体再偏置可以有效地实现为相应类中纪元的增量。下次要锁定此类的实例时,代码会在标头字中检测到不同的值,并将对象重新偏向当前线程。

源代码提示

同步影响 JVM 的多个部分:对象标头的结构在类和中定义,瘦锁的代码集成在解释器和编译器中,类表示膨胀锁。偏置锁定集中在类中。它可以通过标志启用,也可以通过 禁用。默认情况下,它为 Java 6 和 Java 7 启用,但仅在应用程序启动后几秒钟激活。因此,请注意短期运行的微基准测试。如有必要,请使用标志关闭延迟。oopDescmarkOopDescObjectMonitorBiasedLocking-XX:+UseBiasedLocking-XX:-UseBiasedLocking-XX:BiasedLockingStartupDelay=0

逃逸分析 - 维基百科,自由的百科全书 (wikipedia.org)

x86调用约定 - 维基百科 (wikipedia.org)

Java 管理扩展 - 维基百科 (wikipedia.org)

从表到里学习JVM实现 (douban.com)

 

posted @ 2023-08-11 15:43  CharyGao  阅读(11)  评论(0编辑  收藏  举报