首页 > 编程笔记 > Java笔记 阅读:9

Java synchronized锁的粗化和消除(新手必看)

锁消除和锁粗化技术是锁的优化手段之一,属于高频面试点,它们既可以用于单独提问,也可以用于与锁优化问题合并提问。

Java synchronized锁消除

在 Java 程序中,有些锁实际上是不必要的,例如在只会被一个线程使用的数据上加的锁。JVM 在 JIT(Just-In-Time,即时)编译的时候,通过一种叫作逃逸分析的技术,可以检测到这些不必要的锁,然后将其删除。

锁消除主要应用在没有多线程竞争的情况下。具体来说,当一个数据仅在一个线程中使用,或者这个数据的作用域仅限于一个线程时,这个线程对该数据的所有操作都不需要获取锁。在 Java HotSpot 虚拟机中,这种优化主要是通过逃逸分析来实现的。

锁消除技术消除了不必要的锁竞争,从而减少了线程切换和线程调度带来的性能开销。当数据仅在单个线程中使用时,对此数据的所有操作都不需要同步。

在这种情况下,锁操作不仅不会增加安全性,反而会因为增加了额外的执行开销而降低程序的运行效率。

在代码层面上,我们无法直接控制 JVM 进行锁消除优化,这是由 JVM 的 JIT 编译器在运行时动态完成的。然而,我们可以通过编写高质量的代码,使 JIT 编译器更容易识别出可以进行锁消除的场景。例如:
public class LockEliminationTest {
    public String appendString(String str1, String str2, String str3) {
        StringBuffer s = new StringBuffer();
        sb.append(str1).append(str2).append(str3);
        return s.toString();
    }
}
在这段代码中,StringBuffer 实例 s 的作用域仅限于 appendString() 方法。在多线程环境中,不同的线程执行 appendString() 方法会创建各自的 StringBuffer 实例,它们之间互不影响。因此,JIT 编译器会发现这种情况并自动消除 s.append 操作中的锁竞争。

所以在编码时,变量作用域应尽可能小。

锁消除是一种有效的优化手段,它可以帮助我们消除不必要的锁,从而提高程序的运行效率。在日常编程中,我们应该尽量避免在单线程的上下文中使用同步数据结构,从而使锁消除技术得以发挥作用。

Java synchronized锁粗化

锁粗化是一种将多次连续的锁定操作合并为一次操作的优化手段。假如一个线程在一段代码中反复对同一个对象进行获取锁和释放锁,JVM 就会将这些锁的范围扩大,俗称粗化,即在第一次获取锁的位置获取锁,最后一次释放锁的位置释放锁,中间的获取锁和释放锁操作则被省略。

获取锁和释放锁操作本身会带来一定的性能开销,因为每次获取锁和释放锁都可能会涉及线程切换、线程调度等的开销。如果有大量小的同步块频繁地进行获取锁和释放锁,那么这部分开销可能会变得很大,从而降低程序的执行效率。

通过锁粗化,可以将多次获取锁和释放锁操作减少到一次,从而减少这部分开销,提高程序的运行效率。在代码层面上,我们并不能直接控制 JVM 进行锁粗化,因为这是 JVM 在运行时动态进行的优化。不过,我们可以在编写代码时尽量减少使用不必要的同步块,避免频繁获取锁和释放锁。这就为 JVM 的锁粗化优化提供了可能。

示例代码如下:
synchronized (lock) {
    // 同步代码块 1
}
// 其他业务代码
synchronized (lock) {
    // 同步代码块 2
}
JVM 在运行时可能会选择将上述两个小的同步代码块合并成一个大的同步代码块,如下所示。

synchronized (lock) {
    // 同步代码块 1
    // 其他业务代码
    // 同步代码块 2
}
锁粗化是 JVM 提供的一种优化手段,能够有效地提高并发编程的效率。我们在编写并发代码时,应当注意同步块的使用,尽量减少不必要的获取锁和释放锁,从而使锁粗化技术能够发挥作用。

相关文章