Java线程安全问题-模拟对同一银行账户进行取款

本文最后更新于:2021年10月6日 晚上

Java线程安全问题-模拟对同一银行账户进行取款

代码:

package ThreadSafe;

/**
 * 银行账户
 * 不适用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题
 */
public class Account {
    private String actNo;
    private double balance;

    public Account() {
    }

    public Account(String actNo, double balance) {
        this.actNo = actNo;
        this.balance = balance;
    }

    public String getActNo() {
        return actNo;
    }

    public void setActNo(String actNo) {
        this.actNo = actNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }


    //取款方法
    public void withdraw(double money){

        //取款之前的余额
        double before = this.getBalance();
        //取款之后的余额
        double after = before-money;

        //如果加上这段模拟网络延迟,一定会出问题
        //try {
            //Thread.sleep(1000);
        //} catch (InterruptedException e) {
            //e.printStackTrace();
        //}


        //更新余额
        //思考:t1执行到这里,但还没来得及执行这行代码,t2线程进来执行了withdraw方法,此时会发生问题.

        this.setBalance(after);
    }
}
package ThreadSafe;

public class AccountThread extends Thread{
    //两个线程必须共享同一个账户对象
    private Account act;

    //通过构造方法传递过来账户对象
    public AccountThread(Account act){
        this.act = act;

    }
    @Override
    public void run() {
        //run方法的执行表示取款操作,假设存款有5000块
        double money = 5000;
        //取款,多线程并发执行这个方法
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName()+"对"+act.getActNo()+"取款成功,余额"+act.getBalance());
    }
}
package ThreadSafe;

public class Test {
    public static void main(String[] args) {
        //创建账户对象(只创建一个)
        Account act = new Account("act-001",10000);
        //创建两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        //设置name
        t1.setName("t1");
        t2.setName("t2");
        //自动线程取款
        t1.start();
        t2.start();
    }
}

执行结果:

image-20211001162049153

加上模拟延迟代码后执行结果:

image-20211001162140448

解决线程安全问题:

只需要修改Account类:

package ThreadSafe2;

/**
 * 银行账户
 * 不适用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题
 */
public class Account {
    private String actNo;
    private double balance;

    public Account() {
    }

    public Account(String actNo, double balance) {
        this.actNo = actNo;
        this.balance = balance;
    }

    public String getActNo() {
        return actNo;
    }

    public void setActNo(String actNo) {
        this.actNo = actNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }


    //取款方法
    public void withdraw(double money){
        //以下这几行代码必须是线程排队的,不能并发
        //一个线程把这里的代码全部执行结束之后,另一个线程才能进来
        synchronized (this){
            double before = this.getBalance();
            double after = before-money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }

    }
}

运行结果是:

image-20211001163823023

线程同步机制的语法是:
synchronized(){
//线程同步代码块
}

/*
synchronized后面小括号中传入的数据十分重要
这个数据必须是多线程共享的数据.才能达到多线程排队

()中写什么

那要看你想让那些线程同步

假设t1,t2,t3,t4,t5 有5个线程

你只希望t1,t2,t3排队,t4,t5不需要排队,

你一定要在()中写一个t1,t2,t3共享的对象,而这个

对象对于t4,t5来说不是共享的
*/