并发对于开发可以执行多个并发操作的健壮、可扩展的应用程序非常关键。然而,为此需要付出同步方面的代价。由于获取和释放锁的随之而来的开销,它会产生性能成本。为了减轻这些性能成本,JVM 中融入了多种优化,例如偏向锁定、锁定消除、锁定粗化以及轻量级和重量级锁定的概念。
在本文中,我们将更详细地了解这些优化,并探讨它们如何改进多线程 Java 应用程序中的同步。
Java 锁定基础知识
在Java中,块或方法的同步确保一次只有一个线程可以执行代码的关键部分。当考虑多线程环境中的资源共享时,这一点尤其重要。 Java 通过依赖内在锁来实现这一点,或者有时,它们被称为与对象或类关联的监视器,通过使用同步块来帮助管理对线程的访问。
虽然同步是线程安全的必要条件,但当争用较低或完全不存在时,同步的成本可能会相当高。这就是 JVM 优化介入的地方。因此,这降低了锁定成本并提高整体性能。
1。偏向锁定
什么是偏向锁定?
偏向锁是一种旨在减少锁获取开销的优化。它进行了优化,以降低锁获取的成本,锁获取的成本由单个线程主导或大部分由单个线程访问。此类程序通常由同一线程获取和释放锁,而不会与其他线程发生争用。 JVM 可以识别这种模式并将锁偏向于该特定线程;接下来的锁获取几乎是免费的。
偏向锁定如何工作?
如果启用偏向锁定,那么当线程第一次获取锁时,它会使该锁偏向于该线程。线程的身份记录在锁对象的标头中,该线程后续的锁获取不涉及任何同步 - 它们只是检查锁是否偏向当前线程,这是一个非常快速的非阻塞操作.
如果另一个线程尝试获取锁,那么偏向就会被取消,JVM 会退回到标准的无偏锁机制。在此阶段,它现在是一个标准锁,第二个线程必须通过标准锁定过程来获取它。
偏向锁定的好处
性能:同一个线程在偏向锁上的获取几乎是免费锁的获取。
因此,不需要争用处理,因为其他线程没有机会参与获取锁。
较低的开销: 除非发生争用,否则不需要更改锁的状态或修改与同步相关的元数据。
何时使用偏向锁定?
偏向锁在锁主要由同一线程访问的应用程序中很有用,例如单线程应用程序或多线程下锁争用较低的应用程序。它在大多数 JVM 中默认启用。
如何禁用偏向锁定
偏向锁定默认启用,但也可以使用 JVM 标志禁用,如下所示:
-XX:-UseBiasedLocking
2.锁定消除
什么是锁消除?
锁消除是一种非常强大的优化,JVM 完全消除了一些不必要的同步(锁)。它将在 JIT 编译期间检查代码是否有任何机会,其中发现同步是不必要的。当锁仅被一个线程访问时,或者 JVM 将用于同步的对象在不同线程中不共享同一对象时,通常会发生这种情况。一旦 JVM 认为不再需要它,它就会消除锁。
锁消除是如何工作的?
在JIT编译的逃逸分析阶段,JVM检查对象是否仅限于单个线程或仅在本地上下文中使用。如果因为对象没有逃逸创建它的线程的范围而可以删除该对象上的同步,那么情况就会如此。
例如,如果一个对象完全在一个方法中创建和使用(并且不在线程之间共享),那么 JVM 就会意识到没有其他线程可以访问该对象,因此所有同步都是多余的。在这种情况下,JIT 编译器只是完全消除锁。
零锁定开销:消除不必要的同步也将阻止 JVM 首先支付获取和释放锁的成本。
更高的吞吐量:死同步有时会导致应用程序更高的吞吐量,特别是当代码包含许多同步块时。
看一下这段代码:
public void someMethod() { StringBuilder sb = new StringBuilder(); synchronized (sb) { sb.append("Hello"); sb.append("World"); } }
在这种情况下,sb 上的同步是不必要的,因为 StringBuilder 仅在 someMethod 中使用,并且不在其他线程之间共享。通过查看这个,JVM可以执行逃逸分析来移除锁。
3.锁定粗化
什么是锁粗化?
锁粗化是一种优化,其中 JVM 扩展锁的范围以覆盖更多代码块,而不是在循环或小部分代码中连续获取和释放锁。
锁定粗化工作
如果 JVM 发现紧密循环或多个相邻代码块过于频繁地获取和释放锁,它可以通过在循环外或跨多个代码块获取锁来粗化锁。这使得重复获取和释放无锁变得昂贵,并使线程能够持有锁以进行更多迭代。
代码示例:锁定粗化
考虑这个代码片段:
for (int i = 0; i锁粗化将锁获取推到循环之外,因此线程仅获取锁一次:
synchronized (lock) { for (int i = 0; iJVM 可以通过避免更多的锁获取和释放来显着提高性能。
锁定粗化的好处
更少的锁定自由度开销:粗化可以避免锁的获取和释放,特别是在热点代码中,例如已经迭代了数千次的循环。
性能改进:
与不加锁、多次获取和释放此类锁的情况相比,较长时间的锁定可以提高性能。4。轻型和重型锁
JVM 根据线程之间的争用程度使用两种不同的锁定技术。此类技术包括轻量级锁和重量级锁。
轻量级锁定
轻量级锁定发生在没有争用锁的情况下,这意味着只有一个线程试图获取该锁。在这种情况下,JVM 在尝试获取锁时会使用 CAS 操作来优化获取,这可能在没有重量级同步的情况下发生。
重量级锁定
如果多个线程想要获得同一个锁;也就是说,存在争用,JVM 将其升级为重量级锁定。这将涉及在操作系统级别阻塞线程并使用操作系统级别同步原语来管理它们。重量级锁速度较慢,因为它们实际上需要操作系统执行上下文切换以及管理线程。
锁定升级
如果轻量级锁出现争用,JVM 可能会将其升级为重量级锁。这里的升级意味着从快速的用户级锁切换到更昂贵的操作系统级锁,其中包括线程阻塞。
轻量级锁的好处
快速获取锁:在没有争用的情况下,轻量级锁比重量级锁快得多,因为它们避免了操作系统级同步。
减少阻塞:在没有争用的情况下,线程不会阻塞并以较低的延迟线性增加。
重量级锁的缺点
性能开销:重量级锁会产生线程阻塞、上下文切换和唤醒线程的成本,在非常高的争用情况下性能会下降。
所有这些优化都有助于 JVM 提高多线程应用程序的性能,因此开发人员现在可以编写安全的并发代码,而无需牺牲太多同步开销。了解这些优化可以帮助开发人员设计更高效的系统,特别是在因锁定而导致高性能损失的情况下。
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3