关于线程安全问题,有一个经典的问题:银行取钱的问题。银行取钱的基本流程可以分为如下几个步骤:
1、用户输入账号、密码,系统判断用户的账户、密码是否匹配;
2、用户输入取款金额;
3、系统判断账户余额是否大于取款金额;
4、如果余额大于取款金额,取款成功;如果余额小于取款金额,则取款失败。
初步看上去,这个流程确实就我们日常生活中经常见到的,没有任何问题。但是如果将这个流程放到多线程并发的场景下,就有可能出现问题。注意,是有可能,并不是一定。
下面,我们按照上面的流程去编写取款程序,而且我们只是使用2条线程来模拟取钱操作,模拟两人使用同一账户并发取钱问题。当然,我们不管检查账户和密码的操作,仅仅模拟后面3步操作。
package gblw.first;
public class Account {
//封装账号编码、账号余额两个属性
private String accountNo;
private double balance;
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//下面两个方法根据accountNo来计算Account的hashCode和判断equals
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj!=null&&obj.getClass()==Account.class){
Account target=(Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
package gblw.first;
public class DrawThread extends Thread{
//模拟用户账户
private Account account;
//当前取钱先吃所希望取的钱数
private double drawAmount;
public DrawThread(String name,Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
//当多条线程修改同一个共享数据时,将涉及数据安全问题
public void run(){
//账户余额大于取钱数目
if(account.getBalance()>=drawAmount){
//吐出钞票
System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
//修改余额
account.setBalance(account.getBalance()-drawAmount);
System.out.println("\t余额为:"+account.getBalance());
}else{
System.out.println(getName()+"取钱失败!余额不足!");
}
}
}
package gblw.first;
public class TestDraw {
public static void main(String[] args) throws InterruptedException {
//创建一个账户
Account account=new Account("1234567", 1000);
//模拟两个线程对同一个账户取钱
new DrawThread("甲", account, 800).start();
new DrawThread("乙", account, 800).start();
}
}
运行后的结果如下:
乙取钱成功!吐出钞票:800.0
甲取钱成功!吐出钞票:800.0
余额为:200.0
余额为:-600.0
竟然余额为负数,这个肯定是银行不能接受的,这就是多线程高并发情况下引发的线程安全问题。