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

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

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

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

​       有一个银行账户,还有余额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

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,假设有以下的Java代码: ```java public class Counter { private int count; public void increment() { count++; } public int getCount() { return count; } } ``` 这是一个简单的计数器类,其中有一个increment()方法用于对计数器进行加一操作,另外有一个getCount()方法用于获取计数器的当前值。 现在假设多个线程同时对这个计数器进行操作,就会出现并发访问的问题,可能会导致计数器的结果出错。 为了解决这个问题,可以使用Java中的CompletableFuture来实现异步编程,并且保证线安全。具体的实现方式可以通过使用AtomicInteger类来保证计数器的线安全,修改后的代码如下所示: ```java public class Counter { private AtomicInteger count = new AtomicInteger(0); public void increment() { CompletableFuture.runAsync(() -> { count.incrementAndGet(); }); } public CompletableFuture<Integer> getCountAsync() { return CompletableFuture.supplyAsync(() -> { return count.get(); }); } } ``` 在这个代码中,increment()方法使用CompletableFuture.runAsync()方法来异步执行计数器加一操作,并且使用AtomicInteger类来保证线安全。getCountAsync()方法也使用CompletableFuture.supplyAsync()方法来异步获取计数器的值。 使用异步编程可以将计算密集型的操作放在后台线程中进行,从而避免阻塞主线程,提高系统的并发处理能力。同时使用AtomicInteger类可以保证计数器的线安全。当然,异步编程也需要注意一些问题,比如异常处理等,需要根据实际情况进行选择和调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值