Java synchronized 锁机制深度解析与实战指南 - 银行转账案例
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
Java synchronized 锁机制深度解析与实战指南 - 银行转账案例
- 1.前言
- 2. synchronized 的三种使用方式
- 2.1 同步实例方法
- 2.2 同步静态方法
- 2.3 同步代码块
- 3. synchronized 底层实现原理
- 3.1 字节码层面分析
- 3.2 锁升级过程(JDK 1.6+优化)
- 4. 完整实战代码示例
- 4.1 银行账户转账案例
- 4.2 多线程测试类
- 5. synchronized 的高级特性
- 5.1 可重入性
- 5.2 等待/通知机制
- 6. synchronized 最佳实践
- 7. synchronized 与 Lock 的对比
- 8. 总结
1.前言
在 Java
中,synchronized
是最基本且广泛使用的同步原语,用于保证线程对共享资源的互斥访问。它不仅提供了同步性,还通过内存屏障保证了可见性和有序性。
Synchronized
使用的是每个 Java
对象都内置的 “监视器锁”(Monitor Lock,又称 Intrinsic Lock)。每次进入同步方法或同步块时,线程会获取该对象的监视器锁;退出时释放锁,无论正常结束还是异常退出
Synchronized
是 Java
中实现线程同步的核心机制,它提供了以下关键功能:
- 原子性保证:确保临界区代码的不可分割执行
- 内存可见性:强制线程从主内存读取最新数据,保证变量修改的可见性
- 有序性:防止编译器和处理器对代码进行重排序
小伙伴们可以通过本片文章,让你测底了解 synchronized 锁机制。
2. synchronized 的三种使用方式
2.1 同步实例方法
锁对象为当前实例 (this),适用场景:对象内的状态保护
public class Counter {private int count = 0;// 同步实例方法public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}
2.2 同步静态方法
锁对象为当前类的 Class 对象,适用场景:类级别的全局状态保护
public class GlobalCounter {private static int count = 0;// 同步静态方法public static synchronized void increment() {count++;}public static synchronized int getCount() {return count;}
}
2.3 同步代码块
锁对象为指定的引用对象,细粒度锁控制,减少锁竞争
public class FineGrainedLock {private final Object lock1 = new Object();private final Object lock2 = new Object();private int value1 = 0;private int value2 = 0;public void incrementValue1() {synchronized(lock1) {value1++;}}public void incrementValue2() {synchronized(lock2) {value2++;}}
}
3. synchronized 底层实现原理
3.1 字节码层面分析
编译器将同步代码块翻译为 monitorenter
和 monitorexit
指令;同步方法则通过标记方法的 access_flags
来识别为同步方法,执行过程和同步块类似
public void synchronizedMethod();Code:0: aload_01: dup2: astore_13: monitorenter // 进入同步块4: aload_05: dup6: getfield #2 // Field count:I9: iconst_110: iadd11: putfield #2 // Field count:I14: aload_115: monitorexit // 正常退出同步块16: goto 2419: astore_220: aload_121: monitorexit // 异常退出同步块22: aload_223: athrow24: return
3.2 锁升级过程(JDK 1.6+优化)
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 偏向锁:减少无竞争时的开销
- 轻量级锁:使用CAS自旋避免线程阻塞
- 重量级锁:操作系统级别的互斥锁(Mutex Lock)
4. 完整实战代码示例
4.1 银行账户转账案例
下面我们通过经典的银行转账案例来了解 synchronized
public class BankAccount {private final String accountId;private double balance;public BankAccount(String accountId, double balance) {this.accountId = accountId;this.balance = balance;}// 同步方法实现public synchronized void deposit(double amount) {if (amount <= 0) {System.out.println("存款金额必须大于0");return;}balance += amount;System.out.printf("存款成功: 账户 %s 存入 %.2f, 余额: %.2f%n", accountId, amount, balance);}// 同步代码块实现public void withdraw(double amount) {synchronized(this) {if (amount <= 0) {System.out.println("取款金额必须大于0");return;}if (balance < amount) {System.out.printf("取款失败: 账户 %s 余额不足 (%.2f < %.2f)%n", accountId, balance, amount);return;}balance -= amount;System.out.printf("取款成功: 账户 %s 取出 %.2f, 余额: %.2f%n", accountId, amount, balance);}}// 静态同步方法public static synchronized void transfer(BankAccount from, BankAccount to, double amount) {if (from == to) {System.out.println("不能向自己转账");return;}// 使用嵌套同步避免死锁synchronized(from) {synchronized(to) {if (from.balance < amount) {System.out.printf("转账失败: %s 余额不足 (%.2f < %.2f)%n", from.accountId, from.balance, amount);return;}from.balance -= amount;to.balance += amount;System.out.printf("转账成功: %s 向 %s 转账 %.2f%n", from.accountId, to.accountId, amount);}}}public synchronized double getBalance() {return balance;}
}
4.2 多线程测试类
接下来通过多线程来模拟,多个人同时转账的问题
public class BankSimulation {public static void main(String[] args) throws InterruptedException {BankAccount accountA = new BankAccount("A123", 1000);BankAccount accountB = new BankAccount("B456", 2000);// 创建10个存款线程for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 100; j++) {accountA.deposit(10);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}// 创建10个取款线程for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 100; j++) {accountA.withdraw(5);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}// 创建5个转账线程for (int i = 0; i < 5; i++) {new Thread(() -> {for (int j = 0; j < 50; j++) {BankAccount.transfer(accountB, accountA, 20);try {Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}// 等待所有线程完成Thread.sleep(5000);System.out.println("\n最终账户余额:");System.out.printf("账户 %s 余额: %.2f%n", accountA.getAccountId(), accountA.getBalance());System.out.printf("账户 %s 余额: %.2f%n", accountB.getAccountId(), accountB.getBalance());}
}
5. synchronized 的高级特性
5.1 可重入性
同一线程可以多次获取同一把锁(锁计数器在 Monitor 中递增)
public class ReentrantExample {public synchronized void methodA() {System.out.println("进入methodA");methodB(); // 调用另一个同步方法}public synchronized void methodB() {System.out.println("进入methodB");}public static void main(String[] args) {ReentrantExample example = new ReentrantExample();example.methodA();}
}
输出结果: 同一个线程可以多次获取同一个锁
进入methodA
进入methodB
5.2 等待/通知机制
wait() 调用会释放锁并加入 Wait Set
等待;
notify()/notifyAll() 唤醒其中等待的线程,使其进入 Entry Set
,重新竞争锁
public class WaitNotifyExample {private final Object lock = new Object();private boolean condition = false;public void waitForCondition() throws InterruptedException {synchronized(lock) {while (!condition) {System.out.println("条件不满足,开始等待...");lock.wait(); // 释放锁并等待}System.out.println("条件满足,继续执行");}}public void setCondition() {synchronized(lock) {condition = true;lock.notifyAll(); // 通知所有等待线程System.out.println("已设置条件并通知");}}public static void main(String[] args) throws InterruptedException {WaitNotifyExample example = new WaitNotifyExample();new Thread(() -> {try {example.waitForCondition();} catch (InterruptedException e) {e.printStackTrace();}}).start();Thread.sleep(1000); // 确保等待线程先启动new Thread(() -> {example.setCondition();}).start();}
}
6. synchronized 最佳实践
锁对象选择原则
- 使用私有
final
对象:private final Object lock = new Object();
- 避免使用字符串常量或基础类型包装类
- 不要使用可能被重用的对象(如Integer缓存)
减少锁竞争策略
// 锁分解:将大锁拆分为多个小锁
public class SeparateLocks {private final Object readLock = new Object();private final Object writeLock = new Object();private int readCount = 0;private int writeCount = 0;public void incrementRead() {synchronized(readLock) {readCount++;}}public void incrementWrite() {synchronized(writeLock) {writeCount++;}}
}// 锁粗化:减少不必要的锁获取/释放
public void processBatch(List<Item> items) {synchronized(this) {// 合并多个小操作为一个锁块for (Item item : items) {processItem(item);}}
}
避免死锁的四种方法
// 1. 固定顺序获取锁
public void transfer(BankAccount from, BankAccount to, double amount) {BankAccount first = from.compareTo(to) < 0 ? from : to;BankAccount second = from.compareTo(to) < 0 ? to : from;synchronized(first) {synchronized(second) {// 转账逻辑}}
}// 2. 使用tryLock(需配合Lock API)
// 3. 设置超时时间
// 4. 死锁检测与恢复
7. synchronized 与 Lock 的对比
特性 | synchronized | Lock(ReentrantLock) |
---|---|---|
实现机制 | JVM 内置实现 | Java API 实现 |
锁获取方式 | 自动获取释放 | 手动 lock()/unlock() |
可中断性 | 不支持 | 支持 lockInterruptibly() |
超时获取 | 不支持 | 支持 tryLock(timeout) |
公平锁 | 非公平(可设置偏向锁) | 可选公平/非公平 |
条件队列 | 单一条件 | 支持多个 Condition |
性能 | JDK6+ 优化后接近 Lock | 竞争激烈时更优 |
代码复杂度 | 简单 | 需配合 try-finally 确保释放 |
选型建议:
- 简单场景优先使用
synchronized
- 需要高级功能(超时、中断等)时选择
Lock
- 读写分离场景使用
ReadWriteLock
8. 总结
synchronized
是Java
提供的内置锁机制,易用且实用。- 底层通过监视器锁、对象头与字节码指令实现互斥、可重入及可见性保障。
JVM
针对其实现做了多层性能优化。- 使用
wait/notify
时要结合 while 保护,避免虚假唤醒。
通过合理使用 synchronized
锁机制,小伙伴们可以在保证线程安全的同时,构建高性能的并发Java应用。在实际项目中,建议结合具体场景选择最合适的同步策略,并持续进行性能监控和优化。
如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!