模拟夫妻同时取款的线程安全问题
一、问题分析
- 账户余额:10万元
- 小周:取10万元
- 小红:取10万元
- 两人同时取:可能都取成功(线程安全问题)
二、错误示例(线程不安全)
java
public class UnsafeBankAccount {
private double balance = 100000; // 初始余额10万
// 取款方法(线程不安全)
public void withdraw(double amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 开始取款");
// 模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款成功,取款金额: " + amount + ",余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" 取款失败,余额不足");
}
}
public static void main(String[] args) {
UnsafeBankAccount account = new UnsafeBankAccount();
// 小明线程
Thread xiaoming = new Thread(() -> {
account.withdraw(100000);
}, "小明");
// 小红线程
Thread xiaohong = new Thread(() -> {
account.withdraw(100000);
}, "小红");
// 同时启动(模拟同时取款)
xiaoming.start();
xiaohong.start();
try {
xiaoming.join();
xiaohong.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n最终余额: " + account.balance);
}
}
运行结果(可能):
makefile
小明 开始取款
小红 开始取款
小明 取款成功,取款金额: 100000.0,余额: 0.0
小红 取款成功,取款金额: 100000.0,余额: -100000.0
最终余额: -100000.0
问题:两人都取款成功,余额变负数!
三、解决方案
方案1:synchronized同步方法
java
public class SafeBankAccount1 {
private double balance = 100000;
// 同步取款方法
public synchronized void withdraw(double amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 开始取款");
try {
Thread.sleep(100); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款成功,取款金额: " + amount + ",余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" 取款失败,余额不足");
}
}
}
方案2:synchronized同步代码块
java
public class SafeBankAccount2 {
private double balance = 100000;
private final Object lock = new Object(); // 专用锁对象
public void withdraw(double amount) {
synchronized(lock) { // 同步代码块
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 开始取款");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款成功,取款金额: " + amount + ",余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" 取款失败,余额不足");
}
}
}
}
方案3:使用ReentrantLock
java
import java.util.concurrent.locks.ReentrantLock;
public class SafeBankAccount3 {
private double balance = 100000;
private final ReentrantLock lock = new ReentrantLock();
public void withdraw(double amount) {
lock.lock(); // 获取锁
try {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 开始取款");
try {
Thread.sleep(100);
} 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(); // 必须释放锁
}
}
}
四、完整测试程序
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CoupleWithdrawDemo {
public static void main(String[] args) {
System.out.println("=== 夫妻同时取款模拟 ===");
System.out.println("初始余额: 100000元");
System.out.println("小明取款: 100000元");
System.out.println("小红取款: 100000元\n");
// 测试不同方案
testUnsafeAccount(); // 线程不安全
testSafeAccountSync(); // synchronized方案
testSafeAccountLock(); // ReentrantLock方案
}
/**
* 测试线程不安全账户
*/
private static void testUnsafeAccount() {
System.out.println("\n--- 测试1: 线程不安全账户 ---");
UnsafeBankAccount account = new UnsafeBankAccount();
testWithTwoPeople(account);
}
/**
* 测试synchronized安全账户
*/
private static void testSafeAccountSync() {
System.out.println("\n--- 测试2: synchronized安全账户 ---");
SafeBankAccount1 account = new SafeBankAccount1();
testWithTwoPeople(account);
}
/**
* 测试ReentrantLock安全账户
*/
private static void testSafeAccountLock() {
System.out.println("\n--- 测试3: ReentrantLock安全账户 ---");
SafeBankAccount3 account = new SafeBankAccount3();
testWithTwoPeople(account);
}
/**
* 通用测试方法:模拟两人同时取款
*/
private static void testWithTwoPeople(BankAccount account) {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 小明取款
executor.execute(() -> {
account.withdraw(100000);
});
// 小红取款
executor.execute(() -> {
account.withdraw(100000);
});
executor.shutdown();
try {
executor.awaitTermination(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 银行账户接口
*/
interface BankAccount {
void withdraw(double amount);
}
/**
* 线程不安全账户实现
*/
class UnsafeBankAccount implements BankAccount {
private double balance = 100000;
@Override
public void withdraw(double amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 检查余额: 足够");
try {
// 模拟网络延迟、处理时间等
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" ✓ 取款成功,取款金额: " + amount + ",余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" ✗ 取款失败,余额不足,当前余额: " + balance);
}
}
}
/**
* synchronized安全账户实现
*/
class SafeBankAccount1 implements BankAccount {
private double balance = 100000;
@Override
public synchronized void withdraw(double amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 检查余额: 足够");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" ✓ 取款成功,取款金额: " + amount + ",余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" ✗ 取款失败,余额不足,当前余额: " + balance);
}
}
}
/**
* ReentrantLock安全账户实现
*/
class SafeBankAccount3 implements BankAccount {
private double balance = 100000;
private final java.util.concurrent.locks.ReentrantLock lock =
new java.util.concurrent.locks.ReentrantLock();
@Override
public void withdraw(double amount) {
lock.lock();
try {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 检查余额: 足够");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" ✓ 取款成功,取款金额: " + amount + ",余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() +
" ✗ 取款失败,余额不足,当前余额: " + balance);
}
} finally {
lock.unlock();
}
}
}
五、运行结果分析
arduino
=== 夫妻同时取款模拟 ===
初始余额: 100000元
小明取款: 100000元
小红取款: 100000元
--- 测试1: 线程不安全账户 ---
pool-1-thread-1 检查余额: 足够
pool-1-thread-2 检查余额: 足够
pool-1-thread-1 ✓ 取款成功,取款金额: 100000.0,余额: 0.0
pool-1-thread-2 ✓ 取款成功,取款金额: 100000.0,余额: -100000.0
--- 测试2: synchronized安全账户 ---
pool-2-thread-1 检查余额: 足够
pool-2-thread-1 ✓ 取款成功,取款金额: 100000.0,余额: 0.0
pool-2-thread-2 ✗ 取款失败,余额不足,当前余额: