Synchronized 详解及 JDK 版本优化

1. Synchronized 基础

1.1 Synchronized 的使用方式

1.1.1 修饰实例方法
java 复制代码
public class SynchronizedMethod {
    // 修饰实例方法,锁是当前实例对象(this)
    public synchronized void instanceMethod() {
        // 临界区代码
        System.out.println("实例方法锁");
    }
}
1.1.2 修饰静态方法
java 复制代码
public class SynchronizedStatic {
    // 修饰静态方法,锁是当前类的Class对象
    public static synchronized void staticMethod() {
        // 临界区代码
        System.out.println("静态方法锁");
    }
}
1.1.3 修饰代码块
java 复制代码
public class SynchronizedBlock {
    private final Object lock = new Object();
    
    public void method() {
        // 对象锁
        synchronized (this) {
            // 临界区代码
        }
        
        // 类锁
        synchronized (SynchronizedBlock.class) {
            // 临界区代码
        }
        
        // 自定义对象锁
        synchronized (lock) {
            // 临界区代码
        }
    }
}

2. Synchronized 实现原理

2.1 对象头结构

在 JVM 中,对象在内存中的布局分为三部分:

  1. 对象头 (Header)

  2. 实例数据 (Instance Data)

  3. 对齐填充 (Padding)

对象头包含以下信息:

32位 JVM 对象头布局
java 复制代码
|--------------------------------------------------------------|
|                     Mark Word (32 bits)                      |
|--------------------------------------------------------------|
|       HashCode (25 bits)      | Age (4 bits) | Biased (1 bit) | Normal (01) |
|--------------------------------------------------------------|
|       ThreadID (23 bits)      | Epoch (2 bits) | Age (4 bits) | Biased (1 bit) | Biased (01) |
|--------------------------------------------------------------|
|        Lock Pointer (30 bits)               | Heavyweight (00) |
|--------------------------------------------------------------|
|               Unused (30 bits)              | GC (11)         |
|--------------------------------------------------------------|
64位 JVM 对象头布局
java 复制代码
|------------------------------------------------------------------------------|
|                            Mark Word (64 bits)                               |
|------------------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal
|------------------------------------------------------------------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased
|------------------------------------------------------------------------------|
|                    ptr_to_lock_record:62                         | lock:2    | Lightweight Locked
|------------------------------------------------------------------------------|
|                 ptr_to_heavyweight_monitor:62                    | lock:2    | Heavyweight Locked
|------------------------------------------------------------------------------|
|                                                                  | lock:2    | GC
|------------------------------------------------------------------------------|

2.2 Monitor 机制

java 复制代码
public class MonitorMechanism {
    /**
     * 每个Java对象都与一个Monitor关联
     * Monitor包含以下组件:
     * 1. Owner:当前持有锁的线程
     * 2. EntryList:等待获取锁的线程队列
     * 3. WaitSet:调用了wait()的线程队列
     */
    
    public synchronized void method() {
        // 编译后的字节码包含以下指令:
        // monitorenter - 尝试获取Monitor所有权
        // 临界区代码
        // monitorexit  - 释放Monitor
    }
}

2.3 字节码分析

java 复制代码
public class BytecodeAnalysis {
    public synchronized void syncMethod() {
        System.out.println("synchronized method");
    }
    
    public void syncBlock() {
        synchronized (this) {
            System.out.println("synchronized block");
        }
    }
}

// 编译后的字节码示例:
/*
syncMethod:  // 方法级的同步在常量池中标记
  flags: ACC_PUBLIC, ACC_SYNCHRONIZED
  
syncBlock:
  monitorenter  // 获取锁
  ...           // 代码块内容
  monitorexit   // 释放锁
*/

3. JDK 版本优化历程

3.1 JDK 1.0 - 1.5:重量级锁时代

java 复制代码
public class HeavyweightLockJDK15 {
    /**
     * JDK 1.5及之前:Synchronized是完全的重量级锁
     * 缺点:
     * 1. 性能差:需要操作系统内核态切换
     * 2. 无法中断:线程一直阻塞
     * 3. 非公平锁
     * 
     * 工作流程:
     * 1. 线程请求锁
     * 2. 系统调用,从用户态切换到内核态
     * 3. 操作系统管理线程阻塞和唤醒
     * 4. 再次切换回用户态
     */
    
    public static void demonstrate() {
        Object lock = new Object();
        
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取锁");
                try {
                    Thread.sleep(3000); // 模拟长时间持有
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取锁");
            }
        });
        
        t1.start();
        t2.start();
        // t2会一直阻塞,无法中断
    }
}

3.2 JDK 1.6:锁优化革命

3.2.1 偏向锁(Biased Locking)
java 复制代码
public class BiasedLockingJDK16 {
    /**
     * 偏向锁优化:
     * 1. 针对只有一个线程访问同步块的场景
     * 2. 消除无竞争情况下的同步开销
     * 3. 延迟到有竞争时才升级
     * 
     * 启用参数:-XX:+UseBiasedLocking(JDK 15之前默认开启)
     */
    
    public static void testBiasedLock() {
        Object obj = new Object();
        
        // 第一次获取锁,JVM会启用偏向锁
        synchronized (obj) {
            System.out.println("第一次加锁,启用偏向锁");
        }
        
        // 同一线程再次获取锁,直接进入(无需CAS)
        synchronized (obj) {
            System.out.println("同一线程再次加锁,直接进入");
        }
        
        // 另一个线程尝试获取锁,触发偏向锁撤销
        new Thread(() -> {
            synchronized (obj) {
                System.out.println("其他线程加锁,撤销偏向锁升级为轻量级锁");
            }
        }).start();
    }
    
    /**
     * 偏向锁工作流程:
     * 1. 检查对象头中的偏向锁标志和锁标志位
     * 2. 如果可偏向(01),检查线程ID是否指向当前线程
     * 3. 是:直接进入同步块
     * 4. 否:尝试CAS替换线程ID
     * 5. 成功:偏向当前线程
     * 6. 失败:存在竞争,升级为轻量级锁
     */
}
3.2.2 轻量级锁(Lightweight Locking)
java 复制代码
public class LightweightLockingJDK16 {
    /**
     * 轻量级锁优化:
     * 1. 针对线程交替执行同步块的场景
     * 2. 使用CAS操作避免操作系统阻塞
     * 3. 在用户态完成锁获取
     * 
     * 核心机制:对象头Mark Word复制到线程栈帧的锁记录中
     */
    
    public static void testLightweightLock() {
        Object lock = new Object();
        
        // 线程交替执行,使用轻量级锁
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    synchronized (lock) {
                        System.out.println(Thread.currentThread().getName() 
                            + " 第" + j + "次获取锁");
                        try {
                            Thread.sleep(100); // 短时间持有
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        
        /**
         * 轻量级锁加锁流程:
         * 1. 在栈帧中创建锁记录(Lock Record)
         * 2. 将对象头Mark Word复制到锁记录(Displaced Mark Word)
         * 3. CAS尝试将对象头指向锁记录
         * 4. 成功:获取轻量级锁
         * 5. 失败:检查是否重入,否则升级为重量级锁
         * 
         * 解锁流程:
         * 1. CAS将Displaced Mark Word写回对象头
         * 2. 成功:解锁完成
         * 3. 失败:说明已升级为重量级锁,需要唤醒等待线程
         */
    }
}
3.2.3 自旋锁与适应性自旋
java 复制代码
public class SpinLockJDK16 {
    /**
     * 自旋锁优化:
     * 1. 避免线程在用户态和内核态之间切换
     * 2. 线程在等待时执行忙循环(自旋)
     * 
     * 适应性自旋(Adaptive Spinning):
     * 1. 根据前一次自旋等待时间动态调整
     * 2. 如果自旋很少成功,则减少自旋次数
     * 3. 如果自旋经常成功,则增加自旋次数
     * 
     * 参数:
     * -XX:+UseSpinning           // 启用自旋(JDK 1.6默认)
     * -XX:PreBlockSpin=10        // 默认自旋次数
     */
    
    public static void demonstrateAdaptiveSpin() {
        Object lock = new Object();
        
        // 线程1长时间持有锁
        new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1长时间持有锁");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        // 给线程1启动时间
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        
        // 线程2尝试获取锁
        new Thread(() -> {
            long start = System.currentTimeMillis();
            synchronized (lock) {
                long end = System.currentTimeMillis();
                System.out.println("线程2等待时间:" + (end - start) + "ms");
                // 第一次自旋可能失败,JVM会记录并减少后续自旋时间
            }
        }).start();
    }
}
3.2.4 锁消除(Lock Elimination)
java 复制代码
public class LockEliminationJDK16 {
    /**
     * 锁消除优化:
     * 1. JIT编译器进行的优化
     * 2. 基于逃逸分析(Escape Analysis)
     * 3. 消除不可能存在共享资源竞争的锁
     * 
     * 启用参数:
     * -XX:+DoEscapeAnalysis    // 逃逸分析(默认开启)
     * -XX:+EliminateLocks      // 锁消除(默认开启)
     */
    
    // 示例1:StringBuffer局部变量
    public String concatStrings(String s1, String s2, String s3) {
        // StringBuffer是线程安全的,但这里的sb不会逃逸出方法
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        sb.append(s3);
        return sb.toString();  // JIT会消除synchronized锁
    }
    
    // 示例2:无竞争的同步块
    public void noContention() {
        Object lock = new Object();  // 局部对象,不会逃逸
        
        synchronized (lock) {
            // 只有一个线程能访问此方法,锁会被消除
            System.out.println("无竞争的锁会被消除");
        }
    }
    
    // 对比:锁不会被消除的情况
    private final Object sharedLock = new Object();  // 共享对象
    
    public void withContention() {
        synchronized (sharedLock) {
            // 多个线程可能竞争,锁不会消除
            System.out.println("共享锁不会被消除");
        }
    }
}
3.2.5 锁粗化(Lock Coarsening)
java 复制代码
public class LockCoarseningJDK16 {
    /**
     * 锁粗化优化:
     * 1. 将连续的多个锁操作合并为一个
     * 2. 减少锁的获取和释放次数
     * 3. 提升性能(尤其在循环中)
     */
    
    // 优化前:多次锁获取/释放
    public void beforeCoarsening() {
        Object lock = new Object();
        
        // 连续的同步块
        synchronized (lock) {
            System.out.println("操作1");
        }
        
        synchronized (lock) {
            System.out.println("操作2");
        }
        
        synchronized (lock) {
            System.out.println("操作3");
        }
        
        // JIT可能会优化为:
        // synchronized (lock) {
        //     System.out.println("操作1");
        //     System.out.println("操作2");
        //     System.out.println("操作3");
        // }
    }
    
    // 循环中的锁粗化
    public void loopCoarsening() {
        Object lock = new Object();
        
        // 在循环内加锁(可能被粗化到循环外)
        for (int i = 0; i < 1000; i++) {
            synchronized (lock) {
                // 简单操作
                System.out.println(i);
            }
        }
        
        // JIT可能优化为:
        // synchronized (lock) {
        //     for (int i = 0; i < 1000; i++) {
        //         System.out.println(i);
        //     }
        // }
    }
    
    // 注意:锁粗化的边界条件
    public void coarseningBoundary() {
        Object lock = new Object();
        
        // 中间有耗时操作,可能不会被粗化
        synchronized (lock) {
            System.out.println("操作1");
        }
        
        expensiveOperation(); // 耗时操作
        
        synchronized (lock) {
            System.out.println("操作2");
        }
        // 这种情况JIT通常不会粗化
    }
    
    private void expensiveOperation() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.3 JDK 8:进一步优化

java 复制代码
public class SynchronizedJDK8 {
    /**
     * JDK 8 优化:
     * 1. 改进的锁消除和锁粗化
     * 2. 更好的逃逸分析
     * 3. 默认开启所有优化
     * 
     * 重要参数变化:
     * -XX:+DoEscapeAnalysis          // 默认开启
     * -XX:+EliminateLocks            // 默认开启
     * -XX:+EliminateAllocations      // 标量替换,默认开启
     */
    
    // 标量替换(Scalar Replacement)示例
    public void scalarReplacement() {
        Point point = new Point(10, 20);
        int x = point.x;  // 直接使用基本类型,对象被消除
        int y = point.y;
        System.out.println(x + y);
    }
    
    static class Point {
        int x, y;
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

3.4 JDK 15:偏向锁被废弃

java 复制代码
public class BiasedLockDeprecationJDK15 {
    /**
     * JDK 15 重要变化:
     * 1. 默认禁用偏向锁:-XX:-UseBiasedLocking
     * 2. 废弃偏向锁相关代码
     * 3. 未来版本可能移除
     * 
     * 原因:
     * 1. 现代应用竞争激烈,偏向锁很少受益
     * 2. 偏向锁撤销开销大
     * 3. 保持偏向锁状态需要额外的维护
     * 4. 与新的ZGC不兼容
     */
    
    public static void showCurrentDefaults() {
        // JDK 15+ 默认配置:
        // -XX:+UseBiasedLocking false
        // -XX:+UseHeavyMonitors   false(使用轻量级锁)
        
        System.out.println("JDK 15+ 偏向锁默认禁用");
        System.out.println("锁升级路径:无锁 → 轻量级锁 → 重量级锁");
    }
}

4. 现代 JVM 锁状态转换

4.1 完整的锁升级流程(JDK 15+)

java 复制代码
public class ModernLockUpgrade {
    /**
     * 现代JVM锁状态转换(JDK 15+):
     * 
     * 1. 无锁状态 (01)
     *    ↓ 线程请求锁
     * 2. 轻量级锁 (00) ←→ 无锁
     *    ↓ 竞争加剧
     * 3. 重量级锁 (10)
     * 
     * 注意:偏向锁已被废弃
     */
    
    // 演示锁升级过程
    public static void demonstrateLockUpgrade() {
        Object lock = new Object();
        
        // 阶段1:无竞争,使用轻量级锁
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1:轻量级锁");
            }
        }).start();
        
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        
        // 阶段2:轻微竞争,可能保持轻量级锁
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    System.out.println("线程" + Thread.currentThread().getId() 
                        + ":可能轻量级锁");
                }
            }).start();
            try { Thread.sleep(50); } catch (InterruptedException e) {}
        }
        
        // 阶段3:激烈竞争,升级为重量级锁
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    System.out.println("线程" + Thread.currentThread().getId() 
                        + ":可能重量级锁");
                    try {
                        Thread.sleep(100); // 模拟工作
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

5. 性能对比与最佳实践

5.1 性能对比测试

java 复制代码
public class SynchronizedPerformance {
    private static final int ITERATIONS = 10_000_000;
    private int counter = 0;
    private final Object lock = new Object();
    
    // 测试1:无同步(基准)
    public void testNoSync() {
        for (int i = 0; i < ITERATIONS; i++) {
            counter++;
        }
    }
    
    // 测试2:方法级同步
    public synchronized void testMethodSync() {
        for (int i = 0; i < ITERATIONS; i++) {
            counter++;
        }
    }
    
    // 测试3:代码块同步
    public void testBlockSync() {
        for (int i = 0; i < ITERATIONS; i++) {
            synchronized (this) {
                counter++;
            }
        }
    }
    
    // 测试4:粗粒度同步
    public void testCoarseSync() {
        synchronized (this) {
            for (int i = 0; i < ITERATIONS; i++) {
                counter++;
            }
        }
    }
    
    public static void main(String[] args) {
        SynchronizedPerformance test = new SynchronizedPerformance();
        
        // 测试不同场景下的性能
        long start, end;
        
        // 单线程测试
        start = System.currentTimeMillis();
        test.testNoSync();
        end = System.currentTimeMillis();
        System.out.println("无同步: " + (end - start) + "ms");
        
        // 重置
        test.counter = 0;
        
        start = System.currentTimeMillis();
        test.testMethodSync();
        end = System.currentTimeMillis();
        System.out.println("方法同步: " + (end - start) + "ms");
        
        // 更多测试...
    }
}

5.2 最佳实践

java 复制代码
public class SynchronizedBestPractices {
    
    // 1. 尽量减小同步范围
    public void minimizeScope() {
        // 不好:同步整个方法
        // public synchronized void process() { /* 大量代码 */ }
        
        // 好:只同步必要部分
        public void process() {
            // 非同步代码
            // ...
            synchronized (this) {
                // 只同步共享资源访问
            }
            // 更多非同步代码
        }
    }
    
    // 2. 使用不同的锁对象
    public class SeparateLocks {
        private final Object readLock = new Object();
        private final Object writeLock = new Object();
        private int readCount = 0;
        private int writeCount = 0;
        
        public void incrementRead() {
            synchronized (readLock) {
                readCount++;
            }
        }
        
        public void incrementWrite() {
            synchronized (writeLock) {
                writeCount++;
            }
        }
    }
    
    // 3. 避免在循环内同步
    public void avoidSyncInLoop() {
        Object lock = new Object();
        List<String> data = new ArrayList<>();
        
        // 不好:每次迭代都获取/释放锁
        // for (String item : data) {
        //     synchronized (lock) {
        //         process(item);
        //     }
        // }
        
        // 好:批量处理
        synchronized (lock) {
            for (String item : data) {
                process(item);
            }
        }
    }
    
    // 4. 使用private final锁对象
    public class PrivateFinalLock {
        private final Object lock = new Object(); // 私有final,防止外部修改
        
        public void safeMethod() {
            synchronized (lock) {
                // 线程安全
            }
        }
        
        // 避免使用String/Integer等作为锁
        // private final String badLock = "LOCK"; // 可能导致意外同步
    }
    
    // 5. 考虑使用java.util.concurrent
    public void considerConcurrentPackage() {
        // 高竞争场景:使用ReentrantLock
        // ReentrantLock lock = new ReentrantLock();
        // lock.lock();
        // try {
        //     // 临界区
        // } finally {
        //     lock.unlock();
        // }
        
        // 读写分离:使用ReadWriteLock
        // ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    }
    
    private void process(String item) {
        // 处理逻辑
    }
}

6. 监控与调试

6.1 查看锁状态

java 复制代码
public class LockStateInspection {
    
    // 使用jstack查看线程状态
    public static void inspectLockState() {
        Object lock = new Object();
        
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1持有锁");
                    Thread.sleep(5000); // 长时间持有
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取到锁");
            }
        });
        
        t1.start();
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        t2.start();
        
        // 此时运行:jstack <pid> 可以看到:
        // "Thread-0" 状态:TIMED_WAITING (sleeping),持有锁
        // "Thread-1" 状态:BLOCKED (on object monitor),等待锁
    }
    
    // 使用ManagementFactory监控
    public static void monitorWithJMX() throws Exception {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        
        // 查找死锁
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (deadlockedThreads != null) {
            ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
            for (ThreadInfo info : threadInfos) {
                System.out.println("死锁线程: " + info.getThreadName());
                System.out.println("锁信息: " + info.getLockInfo());
                System.out.println("锁拥有者: " + info.getLockOwnerName());
            }
        }
    }
}

6.2 诊断工具

java 复制代码
# 1. jstack - 查看线程栈和锁信息
jstack <pid>

# 2. jcmd - 多功能诊断
jcmd <pid> Thread.print  # 等同于jstack
jcmd <pid> VM.flags      # 查看VM参数

# 3. JConsole - 图形化监控
jconsole

# 4. VisualVM - 高级分析
jvisualvm

# 5. arthas - 阿里开源的Java诊断工具
# 在线诊断,无需重启应用

7. 总结

7.1 优化历程总结

JDK版本 主要优化 影响
1.0-1.5 重量级锁 性能差,需要内核切换
1.6 偏向锁、轻量级锁、自旋锁、锁消除、锁粗化 性能大幅提升
8 改进逃逸分析,默认开启所有优化 进一步优化
15 默认禁用偏向锁 简化锁实现,更好适应现代应用

7.2 当前最佳配置(JDK 17+)

java 复制代码
# 现代JVM推荐配置
-XX:+UseCompressedOops           # 压缩指针(默认开启)
-XX:+DoEscapeAnalysis            # 逃逸分析(默认开启)
-XX:+EliminateLocks              # 锁消除(默认开启)
-XX:+EliminateAllocations        # 标量替换(默认开启)
-XX:-UseBiasedLocking            # 禁用偏向锁(JDK 15+默认)

7.3 性能建议

  1. 优先使用轻量级同步:volatile、原子类

  2. 合理使用synchronized:减小同步范围

  3. 高竞争场景:考虑ReentrantLock

  4. 读写分离:使用ReadWriteLock

  5. 监控和测试:定期检查锁竞争情况

Synchronized经过多年发展,已经从一个性能较差的重量级锁,优化为智能高效的同步机制。理解其工作原理和优化历程,有助于编写高性能的并发程序。

相关推荐
夏幻灵2 小时前
JAVA基础:基本数据类型和引用数据类型
java·开发语言
weixin199701080162 小时前
闲鱼 item_get - 商品详情接口对接全攻略:从入门到精通
java·后端·spring
cike_y2 小时前
Spring-Bean的作用域&Bean的自动装配
java·开发语言·数据库·spring
qq_12498707533 小时前
基于深度学习的蘑菇种类识别系统的设计与实现(源码+论文+部署+安装)
java·大数据·人工智能·深度学习·cnn·cnn算法
谈笑也风生3 小时前
经典算法题型之排序算法(三)
java·算法·排序算法
自己的九又四分之三站台3 小时前
导入数据到OG GraphQL以及创建graph
java·后端·graphql
强子感冒了3 小时前
Java学习笔记:String、StringBuilder与StringBuffer
java·开发语言·笔记·学习
程序员JerrySUN4 小时前
OP-TEE + YOLOv8:从“加密权重”到“内存中解密并推理”的完整实战记录
android·java·开发语言·redis·yolo·架构
+VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vueOA工程项目管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计