Java 线程安全问题

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

一、问题分析

  • 账户余额: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 ✗ 取款失败,余额不足,当前余额:
相关推荐
王中阳Go2 小时前
AI编程实践:规则 + 技能 + 子代理 + 钩子,拆解Trae AI 开发规范的完整落地范式
后端·agent·ai编程
码事漫谈3 小时前
放下代码洁癖,享受挖坑人生
后端
_张有志_3 小时前
手撕 STL:一棵红黑树,凭什么同时撑起 set 和 map?
后端
妙码生花3 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二十七):管理员登录验证码开关
前端·后端·ai编程
她的男孩3 小时前
当 Spring Boot 遇上 Flowable 7:Forge 框架企业级工作流引擎架构全解析
人工智能·后端·程序员
java小白小3 小时前
SpringBoot(15):日志框架选型与配置——Logback 从入门到生产级
后端
庄周de蝴蝶3 小时前
两次考试,总算压线通过系统分析师
后端·架构
孫治AllenSun4 小时前
【MDC】使用 MDC 封装一个 LogMdcUtil 工具类
java·spring boot·后端
神奇小汤圆4 小时前
Java 26正式发布!这3个新特性,让代码量直接减半
后端
再吃一根胡萝卜4 小时前
一亿数据下,如何实时统计“业务员业绩 TOP 10”?别再 GROUP BY 了
后端