欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程并发引起的数据不安全问题简单举例

程序员文章站 2022-05-01 23:06:00
...

多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。

并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥。

多线程应用中如果涉及到多个线程操作共享变量,极有可能出现线程并发导致数据不安全问题,例如银行账户取钱问题:

​       有一个银行账户,还有余额10000元,现在A通过银行卡从中取10000元,而同时另外一个人B通过存折也从这个账户中取10000元。取钱之前,要首先进行判断:如果账户中的余额大于要取的金额,则可以执行取款操作,否则,将拒绝取款。

      ​ 我们假定有两个线程来分别从银行卡和存折进行取款操作,当A线程执行完判断语句后,获得了当前账户中的余额数(10000元),因为余额大于取款金额,所以准备执行取钱操作(从账户中减去10000元),但此时它被线程B打断,然后,线程B根据余额,从中取出10000元,然后,将账户里面的余额减去10000元,然后,返回执行线程A的动作,这个线程将从上次中断的地方开始执行:也就是说,它将不再判断账户中的余额,而是直接将上次中断之前获得的余额减去10000。此时,经过两次的取款操作,账户中的余额为0元,从账面上来看,银行支出了10000元,但实际上,银行支出了20000元。

通过编程,重现以上问题:

账户类:Account.java

public class Account {

	private String num;	//账号
	private double cash; //余额
	
	public Account(String num,double cash) {
		this.num = num;
		this.cash = cash;
	}

	public String getNum() {
		return num;
	}

	public void setNum(String num) {
		this.num = num;
	}

	public double getCash() {
		return cash;
	}

	public void setCash(double cash) {
		this.cash = cash;
	}
}

取款线程类:

/**
 * 用于完成取款操作的线程类
 * @author mrchai
 */
public class AccountManager implements Runnable{

	private Account account;	//需要被取款的账户
	private double money;	//需要取走金额

	public AccountManager(Account account, double money) {
		super();
		this.account = account;
		this.money = money;
	}

	@Override
	public void run() {
			//判断账户中的余额是否足够
			if(account.getCash() >= money){
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//减少账户的余额
				account.setCash(account.getCash() - money);
				System.out.println(Thread.currentThread()+"取款成功,余额:"+account.getCash());
			}else{
				System.out.println(Thread.currentThread()+"取款失败,余额不足!");
			}
	}

测试类Client.java

public class Client {

	public static void main(String[] args) {
		
		//创建账户类,余额10000
		Account a = new Account("0001", 10000);
        //创建Runnable对象,每次取款10000
		AccountManager am = new AccountManager(a, 10000);
		
		Thread t1 = new Thread(am);
		Thread t2 = new Thread(am);	
		//启动两个取钱线程
		t1.start();
		t2.start();
	}
}

结果:

Thread[Thread-0,5,main]取款成功,余额:0.0
Thread[Thread-1,5,main]取款成功,余额:0.0

 根据结果显示,如果时间点恰到好处(两个线程同时进入查询,发现余额足够),两个线程都能成功取钱,这对银行就不公平(损失10000);

但正常情况则是:

Thread[Thread-1,5,main]取款失败,余额不足!
Thread[Thread-0,5,main]取款成功!0.0

因此,想要使它变成正常情况则必须解决由线程并发引起的数据不安全问题。