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 ✗ 取款失败,余额不足,当前余额:
相关推荐
野犬寒鸦1 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德2 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_93592 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子3 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构
源代码•宸4 小时前
大厂技术岗面试之谈薪资
经验分享·后端·面试·职场和发展·golang·大厂·职级水平的薪资
晚霞的不甘5 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
喵叔哟5 小时前
06-ASPNETCore-WebAPI开发
服务器·后端·c#
Charlie_lll6 小时前
力扣解题-移动零
后端·算法·leetcode
打工的小王6 小时前
Spring Boot(三)Spring Boot整合SpringMVC
java·spring boot·后端
80530单词突击赢8 小时前
JavaWeb进阶:SpringBoot核心与Bean管理
java·spring boot·后端