多线程运行环境下肯定存在并发,会存在一个对象被多个线程同时操作(对同一数据的存取)的情况,这样如果不加以控制,很容易存在数据安全问题。
下面以一个银行转账的例子来说明并发可能存在的问题:
模拟账户–Account类:
public class Account {
private String name;//名字
private double money;//余额
//构造方法
public Account (String name,double money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
模拟银行–Bank类:
public class Bank {
/**
* 转账
* @param fromAccount 转出账户
* @param toAccount 转入账户
* @param money 转账金额
* @return
*/
public boolean transfer(Account fromAccount,Account toAccount,double money) {
if (fromAccount.getMoney() >= money) {
fromAccount.setMoney(fromAccount.getMoney() - money);
toAccount.setMoney(toAccount.getMoney() + money);
System.out.println(fromAccount.getName() + "向" + toAccount.getName() + "转账" + money + "元");
return true;
} else {
System.out.println(fromAccount.getName() + "余额不足,转账失败");
return false;
}
}
/**
* 打印余额
* @param account 账户
*/
public void display(Account account) {
System.out.println(account.getName() + ":" + account.getMoney() + "元");
}
}
转账线程–TransferRunnable类:
/**
* 转账线程
* @author 朋
*
*/
public class TransferRunnable implements Runnable{
Bank bank;
private Account fromAccount;
private Account toAccount;
private double money;
private final int DELAY = 10;
@Override
public void run() {
// TODO Auto-generated method stub
bank.transfer(fromAccount, toAccount, money);
try {
Thread.sleep((long) (DELAY * Math.random()));//模拟延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public TransferRunnable (Bank bank,Account fromAccount,Account toAccount,double money) {
this.bank = bank;
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.money = money;
}
}
打印余额线程–DisplayRunnable:
package com.demo;
/**
* 打印余额线程
* @author 朋
*
*/
public class DisplayRunnable implements Runnable {
Bank bank;
private Account account;
private final int DELAY = 10;
public DisplayRunnable(Bank bank,Account account) {
this.bank = bank;
this.account = account;
}
@Override
public void run() {
// TODO Auto-generated method stub
bank.display(account);
try {
Thread.sleep((long) (DELAY * Math.random()));//模拟延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
测试–Test类:
public class Test {
public static void main(String[] args) {
Bank bank = new Bank();//创建银行对象
Account zhangsan = new Account("zhangsan",100);//创建账户对象
Account lisi = new Account("lisi",100);//创建账户对象
//打印输出
System.out.println(zhangsan.getName() + ":" + zhangsan.getMoney() + "元");
System.out.println(lisi.getName() + ":" + lisi.getMoney() + "元");
//模拟并发
for (int i = 0;i < 10;i++) {
new Thread(new TransferRunnable(bank, zhangsan, lisi, 50)).start();
new Thread(new TransferRunnable(bank, lisi, zhangsan, 100)).start();
new Thread(new DisplayRunnable(bank, zhangsan)).start();
new Thread(new DisplayRunnable(bank, lisi)).start();
}
}
}
运行结果(部分):
该例子模拟了zhangsan和lisi两个账户在并发得进行转账和打印余额的操作,其中调用了Thread.sleep()方法模拟了现实中地延迟。从运行结果可以看到,其数据是混乱的,比如上图第七行和第八行,zhangsan向lisi转账50元后,lisi查询到的余额居然是0元,zhnagsan查询到的余额是200元,随着lisi又向zhangsan转账100元。
正常情况下,应该是上图运行结果第七行zhangsan向lisi转账50元后,lisi的余额为100元,zhangsan的余额也为100元。随后lisi向zhangsan转账100元,此时lisi的余额才为0,zhangsan的余额才为200。
上述例子在并发操作时,对相同数据进行存取导致了数据的不一致问题,一般情况下,数据不一致问题有:丢失修改、不可重复读、脏读。
丢失修改
假如zhangsan和lisi余额都为100,此时有两个线程A和B同时发起zhangsan向lisi转账50元的操作。
t1时刻线程A将zhangsan向lisi转账50的结果(zhangsan:100-50元,lisi:100+50元)写入zhangsan和lisi的账户;
t2时刻线程B将zhangsan向lisi转账50的结果(zhangsan:100-50元,lisi:100+50元)写入zhangsan和lisi的账户。
两个线程都执行成功,正常情况下zhangsan的余额应该为0元,lisi的余额为200元。但是zhangsan的金额仅转出了50元到lisi,很明显有一个线程的数据修改操作丢失了。
不可重复读
假如某时刻zhangsan和lisi余额都为100,此时有一个线程A发起zhangsan向lisi转账50元的操作,结果为zhangsan的余额为50元,lisi的余额为150元。
另外一个时刻zhangsan和lisi余额也都为100,此时线程A再次发起zhangsan向lisi转账50元的操作,这时另外一个线程也发起lisi向zhangsan转账30元的操作,结果为zhangsan的余额为80元,lisi的余额为120元。
同一线程A对同一组数据的相同运算结果不同,显然与事实不符,这就是不可重复读。
脏读
假如某时刻zhangsan和lisi余额都为100,线程A对zhangsan发起余额查询操作,同时线程B发起zhangsan向lisi转账50元的操作,此时线程A查询到的是100元,而此时zhangsan实际上已经转出了50元。
要解决并发带来的数据的安全问题,就必须对线程进行并发控制。