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 ✗ 取款失败,余额不足,当前余额:
相关推荐
2601_953720824 小时前
【计算机毕业设计】基于Spring Boot的心理健康咨询与评测系统的设计与实现
java·spring boot·后端
东方小月5 小时前
从零开发一个 Coding Agent(四):使用状态机校验大模型事件流
前端·人工智能·后端
都叫我大帅哥5 小时前
“让AI修就行”?我劝你收回这句话——来自一位Java博主的硬核反常识
后端
whyfail6 小时前
前端学 Spring Boot(8):接口为什么越用越慢?
前端·spring boot·后端
Muscleheng7 小时前
Spring Boot 3.x 集成 DeepSeek 实现 Function Calling(工具调用)
人工智能·spring boot·后端·ai·spring ai·deepseek
@航空母舰7 小时前
SpringBoot通过Map实现天然的策略模式
java·spring boot·后端
IT_陈寒8 小时前
JavaScript的this又双叒叕让我怀疑人生了
前端·人工智能·后端
陈随易8 小时前
MCP协议第5次更新,从打电话到微信聊天的巨大变革
前端·后端·程序员
65岁退休Coder8 小时前
LangChain v1.3.4 笔记 - 06 RAG 检索增强生成
后端