啥是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 的锁升级机制,能在保证线程安全的同时最大限度提升性能。

相关推荐
揣晓丹3 分钟前
JAVA实战开源项目:共享汽车管理系统(Vue+SpringBoot) 附源码
java·开发语言·vue.js·spring boot·开源
阿什么名字不会重复呢6 分钟前
hadoop-配置安装jdk
java·大数据·hadoop
生活百般滋味,人生需要笑对。 --佚名24 分钟前
docker安装redis
java·redis·docker
hxung28 分钟前
spring动态代理是在生命周期的哪个阶段实现的
java·后端·spring
座山雕~42 分钟前
每日一题----------多线程和并发
java·开发语言
支持未来1 小时前
eclipse离线安装Grep console
java·ide·eclipse
哪吒编程1 小时前
2个真实案例,揭示Java并发工具类的致命陷阱,ConcurrentHashMap也不是100%安全
java·后端
机器视觉—ing1 小时前
C# 不同框架如何调用framework 和 net core
java·数据库·c#
李白的粉1 小时前
基于ssm的电子病历系统(全套)
java·毕业设计·ssm·课程设计·源代码·基于ssm的电子病历系统
小九没绝活2 小时前
设计模式-适配器模式
java·设计模式·适配器模式