Java 对 synchronized 锁进行了哪些优化?请举例说明。
参考回答
Java 中的 synchronized
锁在 JDK 1.6 之后进行了大量优化,以提升其性能和使用效率。这些优化包括:
- 偏向锁(Biased Locking):减少无竞争情况下的加锁开销。
- 轻量级锁(Lightweight Locking):避免使用重量级的操作系统互斥锁。
- 自旋锁(Spin Lock):减少线程在获取锁时的上下文切换开销。
- 锁的升级与降级:根据竞争情况动态调整锁的级别(偏向锁 -> 轻量级锁 -> 重量级锁)。
- 锁消除(Lock Elision):JVM 在 JIT 编译时,移除不必要的锁。
- 锁粗化(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
锁的优化包括:
- 偏向锁:减少无竞争情况下的加锁开销。
- 轻量级锁:避免使用重量级锁,采用 CAS 实现高效同步。
- 自旋锁:减少线程上下文切换开销。
- 锁的升级与降级:动态调整锁的级别以适应不同的竞争情况。
- 锁消除:在无必要的同步场景下移除锁。
- 锁粗化:合并频繁的加锁和解锁操作。