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卸载类时锁释放
对比分析

开发者负担
(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

浙公网安备 33010602011771号