Java synchronized 完全指南:从使用场景到底层实现全解析

synchronized 是 Java 内置的线程同步关键字,用于解决多线程并发访问共享资源时的线程安全问题(如竞态条件、数据不一致)。其核心原理是通过互斥锁(排他锁)保证同一时刻只有一个线程能执行特定代码块,同时确保共享变量的可见性有序性(禁止指令重排)。

一、核心作用

  1. 原子性:确保同步代码块 / 方法的执行是 “不可分割” 的,同一时刻只有一个线程进入执行;
  2. 可见性:线程退出同步块时,会将修改后的共享变量刷新到主内存;其他线程进入同步块时,会从主内存重新读取共享变量(避免线程缓存导致的数据不一致);
  3. 有序性:禁止同步块内的指令重排,保证代码执行顺序与编写顺序一致(避免因重排导致的逻辑错乱)。

二、使用场景(3 种形式)

synchronized 可修饰方法代码块,锁的对象分为「对象锁」和「类锁」,核心区别在于锁的作用范围。

1. 修饰实例方法(对象锁)

  • 锁对象:当前实例对象this);
  • 效果:同一时刻,多个线程访问同一个实例的该方法时,会竞争同一把锁;不同实例的方法互不影响。
public class SynchronizedDemo {
    // 实例方法加锁,锁为 this(当前对象)
    public synchronized void instanceMethod() {
        // 临界区:操作共享资源(如实例变量)
        System.out.println(Thread.currentThread().getName() + " 进入实例方法");
        try {
            Thread.sleep(1000); // 模拟业务逻辑
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo1 = new SynchronizedDemo();
        SynchronizedDemo demo2 = new SynchronizedDemo();

        // 线程1和线程2竞争 demo1 的锁(互斥)
        new Thread(() -> demo1.instanceMethod(), "线程1").start();
        new Thread(() -> demo1.instanceMethod(), "线程2").start();

        // 线程3访问 demo2 的方法(与 demo1 无锁竞争,可并行)
        new Thread(() -> demo2.instanceMethod(), "线程3").start();
    }
}

输出结果(线程 1 和 2 互斥,线程 3 独立执行):

线程1 进入实例方法
线程3 进入实例方法
线程2 进入实例方法(延迟1秒后)

2. 修饰静态方法(类锁)

  • 锁对象:当前类的Class 对象(每个类只有一个 Class 对象,全局唯一);
  • 效果:同一时刻,所有线程访问该静态方法时,无论操作哪个实例,都会竞争同一把类锁(全局互斥)。
public class SynchronizedDemo {
    // 静态方法加锁,锁为 SynchronizedDemo.class
    public static synchronized void staticMethod() {
        System.out.println(Thread.currentThread().getName() + " 进入静态方法");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo1 = new SynchronizedDemo();
        SynchronizedDemo demo2 = new SynchronizedDemo();

        // 所有线程竞争同一把类锁(互斥执行)
        new Thread(() -> demo1.staticMethod(), "线程1").start();
        new Thread(() -> demo2.staticMethod(), "线程2").start();
        new Thread(() -> SynchronizedDemo.staticMethod(), "线程3").start();
    }
}

输出结果(三个线程互斥,依次执行):

线程1 进入静态方法
线程2 进入静态方法(延迟1秒后)
线程3 进入静态方法(再延迟1秒后)

3. 修饰代码块(灵活指定锁对象)

  • 锁对象:可手动指定(如 this、类对象、自定义对象),灵活性最高;
  • 核心优势:缩小同步范围(只锁临界区,而非整个方法),减少线程阻塞时间,提升性能。
(1)锁当前实例(this,等价于实例方法加锁)
public void blockThis() {
    // 锁对象为 this,与实例方法加锁效果一致
    synchronized (this) {
        System.out.println(Thread.currentThread().getName() + " 锁 this");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
(2)锁类对象(等价于静态方法加锁)
public void blockClass() {
    // 锁对象为 Class 对象,与静态方法加锁效果一致
    synchronized (SynchronizedDemo.class) {
        System.out.println(Thread.currentThread().getName() + " 锁 Class");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
(3)锁自定义对象(最灵活,用于细粒度同步)

当多个线程需要竞争特定共享资源时,可创建专门的 “锁对象”,避免与其他同步逻辑冲突。

public class SynchronizedDemo {
    // 自定义锁对象(建议用 final 修饰,避免被修改导致锁失效)
    private final Object lock = new Object();
    private int count = 0;

    public void increment() {
        // 只对 count 操作的临界区加锁,缩小同步范围
        synchronized (lock) {
            count++; // 原子操作:读取-修改-写入
        }
    }

    public int getCount() {
        synchronized (lock) { // 读操作也需同步,保证可见性
            return count;
        }
    }
}

三、底层实现原理

synchronized 的底层依赖 JVM 的 Monitor(监视器锁) 机制,分为 “偏向锁”“轻量级锁”“重量级锁” 三个状态(锁升级,从低效到高效),核心是为了平衡 “并发安全性” 和 “性能”。

1. 核心概念:Monitor

  • Monitor 是一个同步工具(可理解为 “锁对象”),每个 Java 对象都天生关联一个 Monitor(通过对象头中的 mark word 指向);
  • 线程进入同步块时,需先获取 Monitor 的所有权(entry set 排队);执行完后释放所有权,其他线程才能竞争。

2. 锁的三种状态(JDK 6+ 优化)

JDK 6 前 synchronized 是 “重量级锁”(依赖操作系统内核态互斥量,切换成本高),JDK 6 引入锁升级机制,按并发强度动态切换状态:

锁状态适用场景实现原理优点
偏向锁单线程重复进入同步块给对象头 mark word 标记当前线程 ID,后续线程进入时直接判断 ID,无需竞争几乎无性能开销
轻量级锁多线程交替进入同步块(无激烈竞争)线程将对象头 mark word 复制到栈帧,用 CAS 尝试修改对象头为 “轻量级锁标记”避免重量级锁的内核态切换
重量级锁多线程同时竞争同步块(激烈竞争)依赖 Monitor 和操作系统互斥量(mutex),未获取锁的线程阻塞(等待队列)完全保证线程安全
锁升级流程:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆,一旦升级为重量级锁,不会回退)。

3. 字节码层面的体现

编译后,synchronized 代码块会生成 monitorenter 和 monitorexit 指令(分别对应进入和退出同步块);同步方法则通过方法常量池中的 ACC_SYNCHRONIZED 标志位实现。

示例字节码(同步代码块):

public void blockDemo();
  Code:
     0: aload_0
     1: getfield      #2                  // 获取自定义锁对象 lock
     4: dup
     5: astore_1
     6: monitorenter  // 进入同步块,获取 Monitor
     7: getstatic     #3                  // 打印逻辑
    10: ...
    21: monitorexit   // 正常退出,释放 Monitor
    22: goto          30
    25: astore_2
    26: aload_1
    27: monitorexit   // 异常退出,仍释放 Monitor(避免死锁)
    28: aload_2
    29: athrow
    30: return

四、常见问题与注意事项

1. 锁对象的选择原则

  • 避免用 String 常量、Integer 等包装类 作为锁对象(常量池复用导致锁冲突);
  • 推荐用 自定义私有 final 对象(如 private final Object lock = new Object()),避免锁对象被修改(final 保证引用不变);
  • 实例变量的同步用 “实例锁”(this 或自定义对象),静态变量的同步用 “类锁”(Class 对象)。

2. 死锁的产生与避免

死锁条件(同时满足则触发):
  1. 多个线程持有不同锁;
  2. 线程互相等待对方释放锁;
  3. 锁不可剥夺;
  4. 锁请求顺序不一致。
避免方式:
  • 统一线程获取锁的顺序(如先获取锁 A,再获取锁 B);
  • 避免在同步块内调用外部方法(可能间接获取其他锁);
  • 用 Lock 接口(如 ReentrantLock)的 tryLock(timeout) 尝试获取锁,超时则释放已持有的锁。

3. synchronized 与 Lock 的区别

特性synchronizedLock(如 ReentrantLock)
锁类型隐式锁(JVM 自动获取 / 释放)显式锁(需手动 lock()/unlock()
灵活性低(仅支持非公平锁,不可中断)高(支持公平锁 / 非公平锁、可中断、超时获取)
性能JDK 6+ 优化后接近 Lock高(适合高并发场景)
功能基础同步(原子性、可见性、有序性)支持条件变量(Condition)、锁降级等高级功能
死锁避免无内置机制可通过 tryLock 超时避免

4. 常见误区

  • 误区 1:认为 synchronized 修饰方法就一定线程安全。错误:若方法内操作的是局部变量(线程私有),无需同步;若操作的是共享资源(如静态变量、实例变量),才需要同步。
  • 误区 2:锁越大越安全。错误:同步范围过大(如整个方法加锁)会导致线程阻塞时间过长,降低并发性能,应只锁 “临界区”(操作共享资源的代码)。
  • 误区 3:忽略读操作的同步。错误:即使是读共享变量,若未同步,可能读取到 “脏数据”(线程缓存未刷新),需通过 synchronized 或 volatile 保证可见性。

五、使用建议

  1. 优先用 同步代码块 而非同步方法,缩小同步范围,提升性能;
  2. 锁对象用 final 修饰,避免被修改导致锁失效;
  3. 低并发、单线程复用场景:依赖 synchronized 的偏向锁 / 轻量级锁优化,性能足够;
  4. 高并发、需要灵活控制(如公平锁、超时获取)场景:用 ReentrantLock 替代 synchronized
  5. 避免嵌套同步(同步块内套同步块),减少死锁风险。

总结

synchronized 是 Java 最基础、最可靠的线程同步机制,通过 Monitor 机制实现互斥锁,同时保证原子性、可见性和有序性。JDK 6+ 的锁升级优化(偏向锁、轻量级锁)使其性能大幅提升,日常开发中优先用于解决简单的线程安全问题(如共享变量修改)。对于复杂场景(如高并发、灵活锁控制),可结合 Lock 接口使用。核心是理解 “锁对象” 和 “同步范围”,避免死锁和性能问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值