Java 对 synchronized 锁进行了哪些优化?请举例说明。

参考回答

Java 中的 synchronized 锁在 JDK 1.6 之后进行了大量优化,以提升其性能和使用效率。这些优化包括:

  1. 偏向锁(Biased Locking):减少无竞争情况下的加锁开销。
  2. 轻量级锁(Lightweight Locking):避免使用重量级的操作系统互斥锁。
  3. 自旋锁(Spin Lock):减少线程在获取锁时的上下文切换开销。
  4. 锁的升级与降级:根据竞争情况动态调整锁的级别(偏向锁 -> 轻量级锁 -> 重量级锁)。
  5. 锁消除(Lock Elision):JVM 在 JIT 编译时,移除不必要的锁。
  6. 锁粗化(Lock Coarsening):将多次加锁操作合并为一个更大范围的锁,减少加锁和解锁的频率。

详细讲解与示例

1. 偏向锁

原理

  • 偏向锁用于减少无竞争的锁操作开销。如果一个线程多次获得同一个锁,JVM 会将锁偏向该线程,下次该线程获取锁时,不需要执行同步操作。

示例代码

public class BiasedLockingExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        long start = System.nanoTime();
        for (int i = 0; i < 1000000; i++) {
            synchronized (lock) {
                // 无实际操作
            }
        }
        long end = System.nanoTime();
        System.out.println("偏向锁操作耗时: " + (end - start) + " 纳秒");
    }
}

特点

  • 偏向锁的开销极低,因为没有线程竞争时,JVM 会跳过加锁操作。
  • 如果检测到锁有竞争(其他线程尝试获取锁),偏向锁会升级为轻量级锁。

关闭偏向锁

  • 可以通过 JVM 参数关闭偏向锁:
    -XX:-UseBiasedLocking
    

2. 轻量级锁

原理

  • 如果偏向锁被撤销,且线程之间的竞争不激烈,JVM 会使用轻量级锁。
  • 轻量级锁通过 CAS(Compare-And-Swap)操作实现,无需线程阻塞。

示例代码

public class LightweightLockingExample {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取了锁");
                try {
                    Thread.sleep(1000); // 模拟工作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取了锁");
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

特点

  • 轻量级锁使用 CAS 进行加锁和解锁,不涉及线程上下文切换。
  • 如果线程竞争加剧,轻量级锁会升级为重量级锁。

3. 自旋锁

原理

  • 如果轻量级锁的竞争较多,但持锁时间短,线程会进行自旋等待(即不停地尝试获取锁),而不是直接阻塞,从而减少线程上下文切换的开销。

示例说明

  • 自旋锁是 JVM 的一种优化机制,开发者无需显式调用。

优点

  • 持锁时间短时性能较高。

缺点

  • 自旋会消耗 CPU,持锁时间长时性能下降。

4. 锁的升级与降级

原理

  • JVM 会根据锁的竞争情况动态调整锁的级别,从偏向锁到轻量级锁再到重量级锁。
  • 锁降级:将重量级锁降级为偏向锁(如在竞争结束后)。

示例

public class LockUpgradeExample {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取了偏向锁");
                try {
                    Thread.sleep(1000); // 持有锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2竞争锁,锁升级为重量级锁");
            }
        });

        t1.start();
        Thread.sleep(500); // 确保线程1先持有锁
        t2.start();

        t1.join();
        t2.join();
    }
}

输出

线程1获取了偏向锁
线程2竞争锁,锁升级为重量级锁

5. 锁消除

原理

  • JVM 在 JIT 编译时,分析代码是否存在实际的线程安全问题。如果没有,JVM 会自动移除无用的锁,减少开销。

示例代码

public class LockElisionExample {
    public void appendString(StringBuilder sb) {
        synchronized (sb) { // 锁可能被消除
            sb.append("Hello");
            sb.append(" World");
        }
    }

    public static void main(String[] args) {
        LockElisionExample example = new LockElisionExample();
        StringBuilder sb = new StringBuilder();
        example.appendString(sb);
        System.out.println(sb.toString());
    }
}

特点

  • 如果 StringBuilder 只在当前线程中使用,锁会被 JVM 消除。

6. 锁粗化

原理

  • JVM 检测到一段代码中频繁地对同一对象加锁和解锁时,会将这些操作合并为一个更大的同步块,减少加锁和解锁的开销。

示例代码

public class LockCoarseningExample {
    public void method() {
        synchronized (this) {
            System.out.println("操作1");
        }
        synchronized (this) {
            System.out.println("操作2");
        }
        synchronized (this) {
            System.out.println("操作3");
        }
    }

    public static void main(String[] args) {
        LockCoarseningExample example = new LockCoarseningExample();
        example.method();
    }
}

JVM 优化后代码

public void method() {
    synchronized (this) {
        System.out.println("操作1");
        System.out.println("操作2");
        System.out.println("操作3");
    }
}

总结

Java 对 synchronized 锁的优化包括

  1. 偏向锁:减少无竞争情况下的加锁开销。
  2. 轻量级锁:避免使用重量级锁,采用 CAS 实现高效同步。
  3. 自旋锁:减少线程上下文切换开销。
  4. 锁的升级与降级:动态调整锁的级别以适应不同的竞争情况。
  5. 锁消除:在无必要的同步场景下移除锁。
  6. 锁粗化:合并频繁的加锁和解锁操作。

发表评论

后才能评论