并发操作的数据安全问题

多线程运行环境下肯定存在并发,会存在一个对象被多个线程同时操作(对同一数据的存取)的情况,这样如果不加以控制,很容易存在数据安全问题。

下面以一个银行转账的例子来说明并发可能存在的问题:

模拟账户–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元的操作。

扫描二维码关注公众号,回复: 5786353 查看本文章

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元。

要解决并发带来的数据的安全问题,就必须对线程进行并发控制。

猜你喜欢

转载自blog.csdn.net/is_Javaer/article/details/84555840
今日推荐