java并发:synchronized 揭秘

存量笔记,常读常新 ~ 再次回顾一下 Java 中的 synchronized 关键词

内存语义

进入 synchronized 块的内存语义是把在 synchronized 块内使用到的变量从线程的工作内存中清除,这样在 synchronized 块内使用变量时不会从线程的工作内存中获取,而是直接从主内存中获取。

退出 synchronized 块的内存语义是把在 synchronized 块内对共享变量的修改刷新到主内存。

 

其实上述内容也是加锁和释放锁的语义

 

synchronized 的内存语义可以解决共享变量内存可见性问题,此外synchronized 经常被用来实现原子性操作(例: signal++ 不是⼀个原⼦操作,需要使⽤ synchronized 给它“上锁”)。

 

典型应用场景

场景:共享资源修改

// 银行账户扣款
public class Account {
    private double balance;
    
    public synchronized void deduct(double amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
}

 

public class Counter {
    private int count = 0;
    
    // 需要显式同步
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

必要性:必须通过锁保证共享状态的一致性

 

重要特性

可重入锁(Reentrant Lock)

public class ReentrantDemo {
    public synchronized void methodA() {
        System.out.println("Enter methodA");
        methodB(); // 嵌套调用同步方法
    }

    public synchronized void methodB() {
        System.out.println("Enter methodB");
    }
}

执行流程:

graph TB
    Thread[线程] -->|获取对象锁| methodA
    methodA -->|无需重新申请锁| methodB
    methodB -->|执行完毕| Release[释放锁]

关键点:同一个线程在持有锁的情况下,可以多次进入同一把锁保护的代码区域【可重入性仅适用于同一把锁(相同对象或 Class 对象)

 

可重入性边界案例

public class StaticLock {
    public synchronized static void staticMethod() {
        instanceMethod(); // 非重入!锁对象不同
    }
    
    public synchronized void instanceMethod() { ... }
}

锁对象差异:

staticMethod 锁定 StaticLock.class
instanceMethod 锁定 this 实例

 

现代替代方案:ReentrantLock

private final ReentrantLock lock = new ReentrantLock();

public void execute() {
    lock.lock(); // 显式获取锁
    try {
        nestedCall();
    } finally {
        lock.unlock();
    }
}

private void nestedCall() {
    lock.lock(); // 可重入获取
    try {
        // ...
    } finally {
        lock.unlock();
    }
}

优势:

支持公平锁
提供 tryLock() 超时机制
可中断的锁获取

 

补充:可重入性设计的必要性

示例1:

public class BankAccount {
    public synchronized void transfer(BankAccount target, int amount) {
        this.withdraw(amount); // 重入
        target.deposit(amount);
    }
    
    public synchronized void withdraw(int amount) { ... }
    public synchronized void deposit(int amount) { ... }
}

解读:

业务逻辑:转账操作需原子性完成扣款和存款
技术支撑:可重入锁保证同步方法嵌套调用不阻塞

 

示例2:

class Base {
    public synchronized void baseMethod() { ... }
}

class Sub extends Base {
    @Override
    public synchronized void baseMethod() {
        super.baseMethod(); // 重入父类同步方法
        // 子类扩展逻辑
    }
}

继承兼容:子类重写方法可通过 super 调用父类同步逻辑

 

JVM 执行机制

每个 Java 对象都与一个 monitor 关联(通过 ObjectMonitor 实现):

// HotSpot JVM 源码 (objectMonitor.hpp)
class ObjectMonitor {
    volatile intptr_t _count;    // 锁重入计数器
    volatile intptr_t _owner;    // 持有锁的线程指针
    volatile intptr_t _recursions; // 重入次数
};

重入过程:

首次加锁:_owner = current_thread, _count = 1
重入时:_count++, _recursions++
解锁时:_count--,直到 _count = 0 时真正释放锁

 

对象级锁(Instance-Level Lock) VS 类级锁(Class-Level Lock)

对象级锁

核心定义:当需要同步非静态方法或非静态代码块时,对象级锁确保在同一个类的特定实例上,只有一个线程能执行该同步代码

关键特性:

锁绑定到具体对象实例(this)
不同实例的锁相互独立
实例销毁时锁自动释放

 

类级锁

核心定义:类级锁用于同步静态方法或静态代码块,确保在任何时候只有一个线程能访问类的静态数据

public class GlobalConfig {
    private static int configVersion = 0;
    
    // 类级锁实现方式1:静态同步方法
    public static synchronized void updateConfig() {
        configVersion++;
    }
    
    // 实现方式2:同步类对象
    public static void safeUpdate() {
        synchronized (GlobalConfig.class) { // 锁定Class对象
            configVersion++;
        }
    }
}

关键特性:

锁绑定到类的Class对象(ClassName.class)
所有实例共享同一把锁
JVM卸载类时锁释放

 

对比分析

image

 

开发者负担

(1)需精准识别临界区
(2)要处理锁嵌套和死锁风险
(3)必须考虑锁粒度(方法锁 vs 代码块锁)

最佳实践:优先使用同步块而非同步方法,明确锁范围

 

并发瓶颈

graph LR
    Thread1-->|等待锁| CriticalSection[临界区]
    Thread2-->|阻塞| CriticalSection
    Thread3-->|阻塞| CriticalSection

锁竞争代价:

(1)线程切换开销:Linux 下约 1-10μs/次
(2)吞吐量衰减:高并发时性能曲线呈断崖式下降

 

实现机制

// HotSpot JVM 锁实现伪代码
void ObjectMonitor::enter() {
    if (TryLock()) return;     // 快速路径
    if (TrySpin()) return;     // 自旋优化
    EnqueueToWaitList();       // 加入等待队列
    Park();                    // 挂起线程(内核切换)
}

性能陷阱:

锁膨胀:无竞争→偏向锁→轻量级锁→重量级锁
等待队列管理:O(n) 复杂度的唤醒操作

 


 

延申阅读

(1)Java synchronized keyword - Java Concurrency - HowToDoInJava

 

posted @ 2026-02-09 13:38  时空穿越者  阅读(2)  评论(0)    收藏  举报