啥是synchronized

synchronized 的 3 种用法详解(附代码实战)​

以下是 Java 中 synchronized 的三种核心用法及详细示例,结合底层原理和实际场景解释其作用机制。


1. 修饰实例方法:锁住当前对象

代码示例

java 复制代码
public class Counter {
    private int count = 0;
    // 实例方法加锁
    public synchronized void add() {
        count++;
    }
}

解释

  • 锁对象 :当前实例(this)。

  • 作用范围 :同一实例的多个线程会竞争锁,不同实例的线程互不影响。例如,Counter c1 = new Counter();Counter c2 = new Counter();add() 方法可被不同线程同时执行。

  • 适用场景:实例变量需要线程安全操作的场景,如计数器、订单处理等。

底层原理

JVM 通过对象头中的 Mark Word 标记锁状态。当一个线程进入 add() 方法时,会获取当前对象的 Monitor 锁,其他线程必须等待锁释放。


2. 修饰静态方法:锁住类对象

代码示例

java 复制代码
public class GlobalCounter {
    private static int count = 0;
    // 静态方法加锁
    public static synchronized void add() {
        count++;
    }
}

解释

  • 锁对象 :类的 Class 对象(如 GlobalCounter.class)。

  • 作用范围 :所有实例共享同一把锁。例如,GlobalCounter.add() 被线程 A 执行时,线程 B 无论通过哪个实例调用 add() 都会被阻塞。

  • 适用场景:静态变量或全局资源的同步操作,如全局配置管理、单例模式等。

底层原理

静态方法的锁对应类的元数据(Class 对象),Monitor 机制会阻止所有实例的并发访问。


3. 修饰代码块:灵活控制锁粒度

代码示例

java 复制代码
public class TransactionService {
    private final Object lock = new Object(); // 自定义锁对象
    private List<String> logs = new ArrayList<>();

    public void logTransaction(String message) {
        // 非同步操作(其他线程可并行执行)
        System.out.println("Preparing log...");

        // 同步代码块(锁定自定义对象)
        synchronized (lock) {
            logs.add(message);
        }
    }
}

解释

  • 锁对象 :可以是任意对象(this、类对象、自定义对象等)。

  • 作用范围

    • ​**synchronized(this)**:锁当前实例,效果等同于修饰实例方法。

    • ​**synchronized(ClassName.class)**:锁类对象,效果等同于修饰静态方法。

    • 自定义对象锁:更细粒度控制,如只锁日志列表而非整个方法。

  • 适用场景:需要减少锁粒度以提升性能的场景,如仅保护共享资源的部分操作。

底层原理

代码块锁通过 monitorentermonitorexit 指令实现,编译器会在代码块前后插入这两个指令,确保锁的获取和释放。


4. 锁对象的选择与优化

关键原则

  • 避免锁字符串或基本类型 :因常量池缓存可能导致意外竞争(如 synchronized("LOCK") 可能全局锁死)。

  • 使用私有锁对象 :如 private final Object lock = new Object();,防止外部代码意外获取锁。

  • 减少锁范围:尽量缩小同步代码块,避免在锁内执行 I/O 或复杂计算。

示例对比

java 复制代码
// 错误示例:锁字符串(风险高)
synchronized ("GLOBAL_LOCK") { ... }

// 正确示例:锁私有对象(安全)
private final Object lock = new Object();
synchronized (lock) { ... }

5. 进阶:锁升级与性能优化

JDK 1.6 后引入锁升级机制,减少锁竞争开销:

  1. 偏向锁:记录第一个获取锁的线程 ID,无竞争时直接进入。

  2. 轻量级锁:通过 CAS 自旋尝试获取锁,适用于低竞争场景。

  3. 重量级锁:竞争激烈时升级为操作系统级互斥锁,线程进入阻塞队列。

代码实战

java 复制代码
// 高竞争场景(可能触发重量级锁)
public class HighContentionDemo {
    private static int counter = 0;
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                synchronized (HighContentionDemo.class) {
                    counter++;
                }
            }).start();
        }
    }
}

此时 JVM 可能直接将锁升级为重量级锁,避免自旋带来的 CPU 浪费。


总结

synchronized 的三种用法对应不同粒度的线程安全控制:

  1. 实例方法锁:保护实例变量,适用于对象级别的并发控制。

  2. 静态方法锁:保护静态变量,全局唯一锁。

  3. 代码块锁:灵活控制锁对象,优化性能的关键手段。

合理选择锁对象和范围,结合 JVM 的锁升级机制,能在保证线程安全的同时最大限度提升性能。

相关推荐
坐吃山猪4 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫5 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao5 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区6 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT7 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy7 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss8 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续9 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0449 小时前
ReAct模式解读
java·ai