Java 中的 synchronized 块是如何工作的?

在 Java 中,synchronized 块是用来实现线程同步的关键机制,确保多个线程在访问共享资源时,能够保持一致性并避免竞争条件(race conditions)。
工作原理

    锁的概念:synchronized 使用**锁(monitor lock)**来实现线程同步。每个对象在 JVM 中都有一个隐式的锁(也称为监视器 monitor),当线程进入 synchronized 块时,它需要先获取该锁。
    锁的行为:
        获取锁:一个线程进入 synchronized 块之前,必须先获得相关对象的锁。如果其他线程已经持有该锁,则当前线程会阻塞,直到锁被释放。
        执行代码:一旦线程获得锁,就可以执行 synchronized 块中的代码。
        释放锁:synchronized 块执行完后,锁会自动释放。即使代码因为异常中断,锁也会释放。

使用方法
1. 同步实例方法

锁定的是当前对象实例。

public synchronized void criticalSection() {
    // 临界区代码
}

等价于:

public void criticalSection() {
    synchronized (this) {
        // 临界区代码
    }
}

2. 同步静态方法

锁定的是类的 Class 对象。

public static synchronized void criticalSection() {
    // 临界区代码
}

等价于:

public static void criticalSection() {
    synchronized (MyClass.class) {
        // 临界区代码
    }
}

3. 同步代码块

可以更灵活地选择锁定的对象,范围更小。

public void criticalSection() {
    Object lock = new Object(); // 也可以是任意对象
    synchronized (lock) {
        // 临界区代码
    }
}

示例:共享计数器

以下是一个典型的线程安全计数器示例:

public class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}

多个线程调用 increment() 和 getCount() 方法时,会通过 synchronized 确保线程安全。
注意点

    锁的粒度:
        锁的粒度越大(例如整个方法),性能开销也会越大。
        推荐使用尽量小的锁范围(例如代码块)来提高并发性能。

    死锁风险: 如果多个线程尝试获取多个锁,并且获取顺序不一致,可能会引发死锁。编写代码时需特别注意锁的顺序。

    性能影响: synchronized 会增加线程切换的开销,但在现代 JVM 中,通过偏向锁和轻量级锁优化,大多数情况下性能已显著提升。

    可重入性: synchronized 是可重入锁,即同一线程可以多次进入已持有的锁。

对比其他锁机制

    ReentrantLock:提供了更灵活的加锁功能,比如可以尝试获取锁(tryLock)或中断等待锁(lockInterruptibly)。
    synchronized:更简单易用,适合大多数普通同步场景。

如果你对某部分感兴趣,或者想了解底层实现细节(如 JVM 的 monitorenter 和 monitorexit),可以深入讨论!

请使用浏览器的分享功能分享到微信等