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 ✗ 取款失败,余额不足,当前余额:
相关推荐
bcbnb1 小时前
iOS应用完整上架App Store步骤与注意事项详解
后端
掘金考拉1 小时前
从原理到实战:JWT认证深度剖析与架构思考(一)——三部分结构的精妙设计
后端
疯狂的程序猴1 小时前
掌握iOS和Android设备应用运行状态监控与性能优化完整教程
后端
IMPYLH1 小时前
Lua 的 tonumber 函数
开发语言·笔记·后端·junit·游戏引擎·lua
acethanlic2 小时前
配置 NeoVim 的代码折叠
后端
淡定__0092 小时前
.NET 中的异步编程:提升应用性能与响应能力
后端
我是天龙_绍2 小时前
为什么springboot依赖不写版本号?
后端
JuiceFS2 小时前
JuiceFS + MinIO:Ariste AI 量化投资高性能存储实践
运维·后端
qq_589568102 小时前
mybatis-plus和springboot项目错误记录
spring boot·后端·mybatis