Java多线程环境下,多个线程同时操作同一个共享账户时,由于线程执行的随机性,会出现竞态条件,导致账户余额计算错误。比如两个线程同时读取账户余额进行取款操作,最后账户余额可能出现负数或者与实际操作不符的情况,因此需要通过同步机制保证共享账户操作的原子性和可见性。

共享账户并发问题示例
首先我们定义一个简单的共享账户类,包含账户余额属性和存取款方法,在不加同步控制的情况下,启动多个线程同时操作该账户,观察出现的问题。
// 共享账户类
class SharedAccount {
// 账户余额
private int balance;
public SharedAccount(int initialBalance) {
this.balance = initialBalance;
}
// 取款方法,未加同步控制
public void withdraw(int amount) {
if (balance >= amount) {
// 模拟业务处理耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
}
}
// 获取当前余额
public int getBalance() {
return balance;
}
}
public class AccountTest {
public static void main(String[] args) {
// 初始化账户,余额1000
SharedAccount account = new SharedAccount(1000);
// 创建两个线程同时取款800
Thread t1 = new Thread(() -> account.withdraw(800), "线程A");
Thread t2 = new Thread(() -> account.withdraw(800), "线程B");
t1.start();
t2.start();
// 等待两个线程执行完成
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终账户余额:" + account.getBalance());
}
}
上述代码运行后,可能出现两个线程都取款成功的情况,最终余额变为-600,这显然不符合实际业务逻辑,这就是没有同步控制导致的并发问题。
基于synchronized的同步方案
synchronized是Java内置的同步关键字,可以用来修饰方法或者代码块,保证同一时间只有一个线程能执行被修饰的代码,从而解决共享资源的并发问题。
修饰实例方法
将共享账户的取款方法用synchronized修饰,此时锁对象就是当前账户实例,同一时间只有一个线程能调用该实例的取款方法。
class SyncAccount1 {
private int balance;
public SyncAccount1(int initialBalance) {
this.balance = initialBalance;
}
// 用synchronized修饰实例方法,锁对象为当前实例
public synchronized void withdraw(int amount) {
if (balance >= amount) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
}
}
public int getBalance() {
return balance;
}
}
修饰代码块
如果不需要对整个方法加锁,可以使用synchronized代码块,指定锁对象,减少锁的粒度,提升性能。
class SyncAccount2 {
private int balance;
// 自定义锁对象
private final Object lock = new Object();
public SyncAccount2(int initialBalance) {
this.balance = initialBalance;
}
public void withdraw(int amount) {
// 同步代码块,锁对象为自定义的lock实例
synchronized (lock) {
if (balance >= amount) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
}
}
}
public int getBalance() {
return balance;
}
}
基于Lock接口的同步方案
Java并发包中的Lock接口提供了比synchronized更灵活的同步控制,其中ReentrantLock是可重入的互斥锁,使用起来更加可控。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockAccount {
private int balance;
// 创建可重入锁实例
private final Lock lock = new ReentrantLock();
public LockAccount(int initialBalance) {
this.balance = initialBalance;
}
public void withdraw(int amount) {
// 获取锁
lock.lock();
try {
if (balance >= amount) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
}
} finally {
// 释放锁,避免死锁
lock.unlock();
}
}
public int getBalance() {
return balance;
}
}
不同同步机制对比
我们可以通过下表对比synchronized和ReentrantLock的特点,根据实际场景选择合适的同步方案:
| 对比维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | Java内置关键字,JVM层面实现 | Java代码实现,基于AQS框架 |
| 锁的释放 | 自动释放,方法执行完成或抛出异常时释放 | 手动释放,需要在finally块中调用unlock方法 |
| 灵活性 | 较低,只能修饰方法或代码块,无法中断等待锁的线程 | 较高,支持尝试获取锁、超时获取锁、可中断获取锁 |
| 公平性 | 非公平锁 | 支持公平锁和非公平锁,构造时传入true为公平锁 |
同步机制使用注意事项
- 锁对象的选择:尽量使用不可变的私有对象作为锁,避免使用字符串常量或者this作为锁,防止锁被外部代码意外获取导致问题。
- 锁的粒度控制:不要随意扩大锁的范围,尽量只锁住操作共享资源的核心代码,减少线程阻塞的时间,提升并发性能。
- 避免死锁:如果多个线程需要获取多个锁,要保证获取锁的顺序一致,同时Lock接口支持超时获取锁,可以有效避免死锁问题。
- 可见性保证:同步机制不仅能保证操作的原子性,还能保证变量的可见性,一个线程修改共享变量后,其他线程能立即看到修改后的值。
在实际的共享账户场景中,根据业务的复杂度和性能要求选择合适的同步机制,就能有效避免多线程操作带来的数据不一致问题,保障账户数据的安全性。
Java多线程共享账户同步机制synchronizedLock修改时间:2026-06-15 06:18:34