返回

深入剖析synchronized第二章:系统级对延迟偏向锁的诠释

闲谈

延迟偏向锁:揭开 Java 同步锁的性能秘密

什么是延迟偏向锁?

当程序中的多个线程同时访问共享数据时,需要使用锁来协调它们的访问。Java 中传统的 synchronized 锁开销很大,尤其是当多个线程争夺同一个锁时。

为了解决这个问题,Java 虚拟机 (JVM) 引入了延迟偏向锁。它是一种更轻量级的锁机制,旨在减少没有竞争的锁定的开销。

延迟偏向锁的工作原理

延迟偏向锁的理念非常简单:只有当多个线程争用一个对象时,才会给该对象加锁。

当一个线程第一次访问一个对象时,JVM 不会立即给它加锁。相反,它会为该对象分配一个延迟偏向锁。延迟偏向锁只是一个标记,表示对象还没有被其他线程锁定。

如果该线程继续独占访问该对象,则延迟偏向锁将保持原样。但是,如果另一个线程尝试访问该对象,则 JVM 将检查延迟偏向锁。

如果延迟偏向锁存在,则 JVM 将尝试将其升级为重量级锁。如果成功,则第二个线程将获得该对象的锁,而第一个线程将被阻塞。如果升级失败,则意味着其他线程已经获得了该对象的锁,第一个线程将被阻塞,直到该锁被释放。

延迟偏向锁的数据结构

延迟偏向锁使用以下数据结构:

  • 对象头: 存储延迟偏向锁标志位和其他元数据。
  • 偏向锁记录: 包含指向偏向线程和时间戳的信息。
  • 锁记录: 存储锁的状态和指向持有线程的信息。

延迟偏向锁的算法

延迟偏向锁的算法如下:

  1. 线程访问对象时,检查对象头是否有延迟偏向锁标志位。
  2. 如果有延迟偏向锁,则尝试升级为重量级锁。
  3. 如果升级成功,则获取锁;如果失败,则等待其他线程释放锁。
  4. 线程释放锁时,检查是否有其他线程等待锁。如果有,则将其唤醒。

延迟偏向锁的性能影响

延迟偏向锁可以显著提高 synchronized 锁的性能。它通过以下方式实现:

  • 减少锁争用: 只有在存在竞争时才会给对象加锁,从而减少了锁争用。
  • 降低锁开销: 延迟偏向锁是一种轻量级锁,仅在必要时升级为重量级锁。
  • 提高吞吐量: 减少锁争用和开销可以提高程序的吞吐量。

代码示例

以下 Java 代码示例展示了延迟偏向锁的使用:

class Counter {
    private int count = 0;

    public int increment() {
        synchronized (this) {
            return ++count;
        }
    }
}

在上面示例中,synchronized 会在每次调用 increment() 方法时给 count 对象加锁。

class CounterWithBias {
    private int count = 0;

    public int increment() {
        if (Thread.currentThread() != biasThread) {
            synchronized (this) {
                biasThread = Thread.currentThread();
                return ++count;
            }
        } else {
            return ++count;
        }
    }
    private Thread biasThread = null;
}

在上面示例中,我们手动实现了延迟偏向锁。只有当多个线程争用 count 对象时,才会给它加锁。

常见问题解答

  1. 什么是锁升级? 锁升级是指将延迟偏向锁转换为重量级锁的过程。
  2. 什么时候会发生锁升级? 当多个线程尝试访问同一个对象时,会发生锁升级。
  3. 延迟偏向锁总是比重量级锁好吗? 对于没有竞争的对象,延迟偏向锁更好。对于有竞争的对象,重量级锁更好。
  4. JVM 如何决定是否使用延迟偏向锁? JVM 根据对象的历史访问模式来决定。
  5. 如何禁用延迟偏向锁? 可以在 JVM 启动参数中使用 -XX:-UseBiasedLocking 选项禁用延迟偏向锁。

总结

延迟偏向锁是 Java 虚拟机中一种巧妙的机制,它可以显著提高 synchronized 锁的性能。通过延迟给对象加锁,它减少了锁争用,降低了锁开销,并提高了程序的吞吐量。虽然延迟偏向锁不是灵丹妙药,但它对于优化高并发程序至关重要。