Java 线程安全问题

47 阅读12分钟

模拟夫妻同时取款的线程安全问题

一、问题分析

  • 账户余额:10万元
  • 小周:取10万元
  • 小红:取10万元
  • 两人同时取:可能都取成功(线程安全问题)

二、错误示例(线程不安全)

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);
    }
}

运行结果(可能):

小明 开始取款
小红 开始取款
小明 取款成功,取款金额: 100000.0,余额: 0.0
小红 取款成功,取款金额: 100000.0,余额: -100000.0

最终余额: -100000.0

问题:两人都取款成功,余额变负数!


三、解决方案

方案1:synchronized同步方法

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同步代码块

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

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();  // 必须释放锁
        }
    }
}

四、完整测试程序

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();
        }
    }
}

五、运行结果分析

=== 夫妻同时取款模拟 ===
初始余额: 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 ✗ 取款失败,余额不足,当前余额:

三种方案的优缺点和最佳方案分析

一、方案对比详细分析

方案1:synchronized同步方法

优点:
1. **实现简单** - 只需加一个关键字
   public synchronized void withdraw(double amount) {
       // 方法体自动同步
   }

2. **自动管理锁** - JVM自动加锁/释放锁
   // 进入方法自动加锁,退出方法自动释放
   // 包括异常退出也会自动释放锁

3. **可重入** - 同一线程可重复获取锁
   public synchronized void methodA() {
       methodB();  // 可以调用另一个同步方法
   }
   public synchronized void methodB() {
       // 同一线程不会死锁
   }

4. **保证可见性** - 自动处理内存屏障
   private double balance;  // 对synchronized方法可见
缺点:
1. **性能较差** - 锁粒度较粗
   public synchronized void withdraw(double amount) {
       // 整个方法被锁定,即使只有部分代码需要同步
       checkValid();      // 不需要同步
       validateAmount();  // 不需要同步
       updateBalance();   // 需要同步 ← 应该只锁这里
       sendNotification(); // 不需要同步
   }

2. **无法中断** - 线程会一直阻塞等待
   // 线程A获取锁
   // 线程B等待锁 ← 无法中断,只能一直等

3. **无法设置超时** - 可能造成死锁
   // 如果持有锁的线程卡住,其他线程永远等不到

4. **不够灵活** - 只能有一个条件等待队列
   // 所有等待线程都在同一个队列

方案2:synchronized同步代码块

优点:
1. **锁粒度细** - 只同步必要代码
   public void withdraw(double amount) {
       // 非同步代码
       checkValid();
       validateAmount();
       
       synchronized(this) {  // 只锁核心部分
           updateBalance();
       }
       
       // 非同步代码
       sendNotification();
   }

2. **可以使用不同锁对象** - 提高并发度
   private final Object balanceLock = new Object();
   private final Object logLock = new Object();
   
   public void transfer() {
       synchronized(balanceLock) {  // 只锁余额
           updateBalance();
       }
       
       synchronized(logLock) {      // 只锁日志
           writeLog();
       }
   }

3. **减少锁竞争** - 不同资源用不同锁
缺点:
1. **需要手动选择锁对象** - 容易出错
   // ❌ 错误:锁错了对象
   synchronized(new Object()) {  // 每次锁不同对象
       // 实际上没有同步效果
   }

2. **代码复杂度增加** - 需要显式管理锁范围
   synchronized(lock) {
       if (condition) {
           return;  // 可能忘记某些清理
       }
       // 业务逻辑
   }

3. **和synchronized方法有同样的其他限制**

方案3:ReentrantLock

优点:
1. **功能强大** - 支持多种特性
   ReentrantLock lock = new ReentrantLock();
   lock.lockInterruptibly();  // 可中断获取锁
   lock.tryLock(1, TimeUnit.SECONDS);  // 尝试获取,可超时

2. **公平锁支持** - 避免线程饥饿
   ReentrantLock fairLock = new ReentrantLock(true);  // 公平锁
   // 按请求顺序获取锁

3. **多个条件变量** - 更精细的线程通信
   Condition notEmpty = lock.newCondition();
   Condition notFull = lock.newCondition();
   // 不同条件可以分开等待/唤醒

4. **性能更好** - 在竞争激烈时表现更好
   // 使用CAS和队列优化

5. **锁信息可查询** - 便于调试
   lock.isLocked();      // 锁是否被持有
   lock.isHeldByCurrentThread();  // 当前线程是否持有
   lock.getQueueLength();  // 等待队列长度
缺点:
1. **必须手动释放锁** - 容易忘记
   lock.lock();
   try {
       // 业务逻辑
       if (error) {
           return;  // ❌ 可能忘记释放锁
       }
   } finally {
       lock.unlock();  // ✅ 必须放在finally中
   }

2. **代码更复杂** - 需要try-finally// synchronized只需要一个关键字
   // ReentrantLock需要4行模板代码

3. **容易死锁** - 如果解锁顺序不对
   lock1.lock();
   lock2.lock();  // 可能死锁
   // 应该总是按固定顺序获取锁

4. **学习成本高** - 需要理解更多概念

二、银行取款场景最佳方案分析

场景需求分析:

// 银行账户取款要求:
1. **线程安全** - 绝对保证数据一致性
2. **性能中等** - 并发不会特别高
3. **简单可靠** - 金融系统稳定第一
4. **便于维护** - 代码要清晰易懂

方案评分(1-5分):

评估维度synchronized方法synchronized块ReentrantLock
安全性5分(自动管理)5分4分(需手动释放)
性能3分(锁粒度粗)4分5分
代码简洁5分(最简单)4分3分
灵活性2分(功能有限)3分5分
可维护性5分(最易懂)4分3分
总分20分20分20分

三、最佳方案推荐:synchronized代码块

为什么选择synchronized代码块?

public class BankAccount {
    private double balance = 100000;
    private final Object lock = new Object();  // 专用锁对象
    
    /**
     * 最佳实践:synchronized代码块
     * 原因1:锁粒度合适,只保护核心逻辑
     * 原因2:代码清晰,容易理解
     * 原因3:JVM自动优化,性能足够
     * 原因4:避免忘记释放锁的风险
     */
    public boolean withdraw(double amount) {
        // 参数校验(不需要同步)
        if (amount <= 0) {
            throw new IllegalArgumentException("金额必须大于0");
        }
        if (amount > 100000) {  // 单笔限额
            throw new IllegalArgumentException("超过单笔限额");
        }
        
        // 核心业务逻辑需要同步
        synchronized(lock) {
            // 双重检查(防止条件变化)
            if (balance < amount) {
                return false;
            }
            
            // 模拟业务处理时间
            try {
                Thread.sleep(10);  // 模拟数据库操作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
            
            // 更新余额
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + 
                " 取款成功,余额: " + balance);
            return true;
        }
        
        // 后续处理(不需要同步)
        // sendSMS();  // 发送短信通知
        // writeLog(); // 写入日志
    }
}

最佳方案的优点:

  1. 性能与安全的平衡

    // 锁范围最小化
    synchronized(lock) {
        // 只锁必要的2-3行代码
        if (balance >= amount) {
            balance -= amount;
        }
    }
    // 非同步部分可以并发执行
    
  2. 避免常见错误

    // ✅ 正确:使用专用锁对象
    private final Object lock = new Object();
    
    // ❌ 错误:使用字符串常量(可能与其他类冲突)
    private final String LOCK = "LOCK";
    
    // ❌ 错误:锁this(可能被外部同步干扰)
    synchronized(this) { }
    
  3. 便于维护和调试

    // 锁对象明确
    private final Object balanceLock = new Object();
    private final Object auditLock = new Object();
    
    // 不同操作用不同锁,减少竞争
    public void complexOperation() {
        synchronized(balanceLock) {
            updateBalance();
        }
        synchronized(auditLock) {
            writeAuditLog();
        }
    }
    

四、生产级完整实现

版本1:基础版(推荐)

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 生产环境银行账户(最佳实践)
 */
public class ProductionBankAccount {
    
    // 余额(使用volatile保证可见性)
    private volatile double balance;
    
    // 专用锁对象(final防止被修改)
    private final Object balanceLock = new Object();
    
    // 交易流水号生成器
    private final AtomicLong transactionIdGenerator = new AtomicLong(1);
    
    // 日期格式化器(线程安全)
    private static final DateTimeFormatter DATE_FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    
    public ProductionBankAccount(double initialBalance) {
        if (initialBalance < 0) {
            throw new IllegalArgumentException("初始余额不能为负数");
        }
        this.balance = initialBalance;
    }
    
    /**
     * 安全取款方法(生产级实现)
     * @param amount 取款金额(必须大于0)
     * @param userId 用户ID(用于日志)
     * @return 交易结果对象
     */
    public WithdrawalResult safeWithdraw(double amount, String userId) {
        // 参数校验(不需要同步)
        validateWithdrawal(amount, userId);
        
        long startTime = System.nanoTime();
        long transactionId = transactionIdGenerator.getAndIncrement();
        
        try {
            // 核心同步代码块(最小范围)
            synchronized(balanceLock) {
                // 双重检查(防止条件变化)
                if (balance < amount) {
                    return WithdrawalResult.failed(
                        transactionId, userId, amount, balance,
                        "余额不足", getCurrentTime()
                    );
                }
                
                // 模拟业务处理(实际是数据库操作)
                simulateBusinessProcess();
                
                // 更新余额
                double oldBalance = balance;
                balance -= amount;
                double newBalance = balance;
                
                long endTime = System.nanoTime();
                long duration = (endTime - startTime) / 1_000_000; // 毫秒
                
                // 记录成功结果
                return WithdrawalResult.success(
                    transactionId, userId, amount,
                    oldBalance, newBalance,
                    getCurrentTime(), duration
                );
            }
        } catch (Exception e) {
            // 异常处理
            return WithdrawalResult.failed(
                transactionId, userId, amount, balance,
                "系统错误: " + e.getMessage(), getCurrentTime()
            );
        }
    }
    
    /**
     * 参数校验
     */
    private void validateWithdrawal(double amount, String userId) {
        if (amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于0");
        }
        if (userId == null || userId.trim().isEmpty()) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        if (amount > 50000) {  // 单笔限额5万
            throw new IllegalArgumentException("超过单笔取款限额");
        }
    }
    
    /**
     * 模拟业务处理(实际是数据库操作、风险检查等)
     */
    private void simulateBusinessProcess() {
        try {
            // 模拟各种检查和处理时间
            Thread.sleep(5);  // 风险检查
            Thread.sleep(3);  // 反洗钱检查
            Thread.sleep(2);  // 数据库操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("处理被中断", e);
        }
    }
    
    /**
     * 获取当前时间戳
     */
    private String getCurrentTime() {
        return LocalDateTime.now().format(DATE_FORMATTER);
    }
    
    /**
     * 查询余额(不需要同步,因为balance是volatile)
     */
    public double getBalance() {
        return balance;
    }
    
    /**
     * 交易结果类(不可变)
     */
    public static class WithdrawalResult {
        private final long transactionId;
        private final String userId;
        private final double amount;
        private final boolean success;
        private final String message;
        private final String timestamp;
        private final double oldBalance;
        private final double newBalance;
        private final long processingTimeMs;
        
        // 构造器、工厂方法、getter等...
        
        public static WithdrawalResult success(long transactionId, String userId,
                double amount, double oldBalance, double newBalance,
                String timestamp, long processingTimeMs) {
            return new WithdrawalResult(transactionId, userId, amount,
                true, "取款成功", timestamp,
                oldBalance, newBalance, processingTimeMs);
        }
        
        public static WithdrawalResult failed(long transactionId, String userId,
                double amount, double balance,
                String reason, String timestamp) {
            return new WithdrawalResult(transactionId, userId, amount,
                false, reason, timestamp,
                balance, balance, 0);
        }
    }
}

版本2:高级版(带监控)

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 高级银行账户(带性能监控和超时控制)
 * 适用于高并发场景
 */
public class AdvancedBankAccount {
    
    private double balance;
    
    // 使用ReentrantLock(因为需要tryLock和监控)
    private final ReentrantLock lock = new ReentrantLock(true);  // 公平锁
    
    // 监控指标
    private long totalWithdrawals = 0;
    private long failedWithdrawals = 0;
    private long totalLockWaitTime = 0;  // 纳秒
    
    /**
     * 高级取款方法(带超时和监控)
     */
    public WithdrawalResult advancedWithdraw(double amount, String userId,
                                            long timeout, TimeUnit unit) {
        long startTime = System.nanoTime();
        boolean locked = false;
        
        try {
            // 尝试获取锁(带超时)
            locked = lock.tryLock(timeout, unit);
            
            if (!locked) {
                // 获取锁超时
                failedWithdrawals++;
                return WithdrawalResult.failed("获取锁超时");
            }
            
            // 记录等待时间
            long waitTime = System.nanoTime() - startTime;
            totalLockWaitTime += waitTime;
            
            // 业务逻辑
            if (balance >= amount) {
                simulateProcess();
                balance -= amount;
                totalWithdrawals++;
                
                return WithdrawalResult.success(amount, balance);
            } else {
                failedWithdrawals++;
                return WithdrawalResult.failed("余额不足");
            }
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            failedWithdrawals++;
            return WithdrawalResult.failed("操作被中断");
        } finally {
            if (locked) {
                lock.unlock();
            }
        }
    }
    
    /**
     * 获取监控指标
     */
    public MonitoringStats getMonitoringStats() {
        return new MonitoringStats(
            totalWithdrawals,
            failedWithdrawals,
            totalLockWaitTime,
            lock.getQueueLength(),
            lock.isLocked()
        );
    }
    
    // 监控统计类
    public static class MonitoringStats {
        private final long totalWithdrawals;
        private final long failedWithdrawals;
        private final long totalLockWaitTime;
        private final int queueLength;
        private final boolean isLocked;
        
        // 构造器、getter等...
    }
}

五、选择决策树

需要实现多线程同步吗?
├── 不需要 → 不用任何同步
│
├── 需要 → 继续判断
│   ├── 是简单计数器/标志吗?
│   │   ├── 是 → 使用Atomic类
│   │   └── 否 → 继续判断
│   │
│   ├── 需要高级功能吗?(超时、中断、公平锁)
│   │   ├── 是 → 使用ReentrantLock
│   │   └── 否 → 继续判断
│   │
│   └── 是典型业务逻辑同步吗?
│       ├── 是 → 使用synchronized代码块 ← ★ 推荐
│       └── 否 → 根据具体情况选择

六、最终建议

对小明小红取款问题的最佳方案:

// 方案选择:synchronized代码块
// 原因:
// 1. 银行账户并发不会特别高(不是秒杀场景)
// 2. 代码清晰易懂,维护成本低
// 3. 性能完全足够
// 4. 避免手动管理锁的复杂性

public class BestBankAccount {
    private double balance = 100000;
    private final Object lock = new Object();
    
    public String withdraw(String person, double amount) {
        // 快速失败检查(不需要锁)
        if (amount <= 0) {
            return person + ": 金额无效";
        }
        
        synchronized(lock) {
            if (balance >= amount) {
                // 模拟处理时间
                try { Thread.sleep(50); } catch (InterruptedException e) {}
                
                balance -= amount;
                return person + ": 取款成功,余额 " + balance;
            } else {
                return person + ": 余额不足";
            }
        }
    }
}

// 测试
public static void main(String[] args) throws InterruptedException {
    BestBankAccount account = new BestBankAccount();
    
    // 小明和小红同时取款
    Thread xiaoming = new Thread(() -> {
        System.out.println(account.withdraw("小明", 100000));
    });
    
    Thread xiaohong = new Thread(() -> {
        System.out.println(account.withdraw("小红", 100000));
    });
    
    // 几乎同时启动
    xiaoming.start();
    xiaohong.start();
    
    xiaoming.join();
    xiaohong.join();
    
    // 输出:
    // 小明: 取款成功,余额 0
    // 小红: 余额不足
}

一句话总结:

对于大多数业务场景(包括银行取款),synchronized代码块是最佳选择——在安全性、性能和可维护性之间取得了最佳平衡。