线程同步问题

线程并发

实际开发中,有很多场景都会出现多个线程共享相同内存空间(变量)的情况,由于多个线程并发操作相同的内存空间,此时就极有可能出现数据不安全的问题,比如经典的银行取钱问题:

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

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

线程同步问题_第1张图片

编码重现问题:

Account.java

   /**
     * 账号类
     * @author mrchai
     *
     */
    public class Account {
    	private int id;
    	private double money;
    	public Account() {
    		// TODO Auto-generated constructor stub
    	}
    
    	public Account(int id, double money) {
    		super();
    		this.id = id;
    		this.money = money;
    	}
    
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    	}
    
    	public double getMoney() {
    		return money;
    	}
    
    	public void setMoney(double money) {
    		this.money = money;
    	}	
    }
    

AccountManager类

    public class AccountManager {
    
    	//需要被操作的账户对象
    	private Account account;
    	
    	public AccountManager(Account account) {
    		super();
    		this.account = account;
    	}
    
    	public  void getMoney(double cash){
    		System.out.println("线程进入:"+Thread.currentThread().getName());
    		//判断余额是否足够
    		if(account.getMoney() >= cash){
    			double d = account.getMoney() - cash;
    			System.out.println("取款成功,取款:"+cash+",余额"+d);
    			account.setMoney(d);
    		}else{
    			System.out.println("取款失败,余额不足");
    		}
    	}
    }

PeopleThread类

  public class PeopleThread extends Thread{
    
    	private AccountManager manager;
    	private double cash;
    	
    	public PeopleThread(AccountManager manager,double cash) {
    		super();
    		this.manager = manager;
    		this.cash = cash;
    	}
    
    	@Override
    	public void run() {
    		manager.getMoney(cash);
    	}
    	
    	
    	public static void main(String[] args) {
    		//初始化一个账户(确保只有一个账户)
    		Account a = new Account(1,10000);
    		AccountManager manager = new AccountManager(a);
    		
    		//创建两个线程对象,传入操作账户的管理对象(唯一)和取款金额
    		PeopleThread t1 = new PeopleThread(manager,10000);
    		PeopleThread t2 = new PeopleThread(manager,10000);
    
    		t1.start();
    		t2.start();
    		
    	}
    }

结果

线程进入:Thread-1
线程进入:Thread-0
取款成功,取款:10000.0,余额0.0
取款成功,取款:10000.0,余额0.0

通过观察结果,得知,账户实际只有10000元,但是两个线程都取款成功,这就出现了线程并发数据不一致的严重问题,如何解决问题?

Synchronized

以上银行取款问题只是众多线程并发产生的安全问题中的一种,还有很多情况,比如:秒杀,抢券,抢票;因此在这问题产生后,就需要一种排队机制的引入;在Java中每一个对象都存在一个互斥锁(排他锁,对象锁),具体的使用是通过synchronizad关键字将对象锁定,此时,如果对象被一个线程锁定,则其他线程无法在操作当前对象,只有等待拥有该对象锁的线程释放锁之后,其他线程才能使用该对象。

以上程序可以如下处理:

   public void getMoney(double cash){
        //将对象锁定,当前线程释放对象锁之前,其他线程无法访问
        synchronized (account) {
            System.out.println("线程进入:"+Thread.currentThread().getName());
            //判断余额是否足够
            if(account.getMoney() >= cash){
                double d = account.getMoney() - cash;
                System.out.println("取款成功,取款:"+cash+",余额"+d);
                account.setMoney(d);
            }else{
                System.out.println("取款失败,余额不足");
            }
        }
    }

以上操作即称之为线程同步。因此,synchronized语句块也称之同步块,synchronized不仅可以用于同步块,还能适用于同步方法,即在方法的声明上使用该关键字,如下

  public synchronized void getMoney(double cash){
        System.out.println("线程进入:"+Thread.currentThread().getName());
        //判断余额是否足够
        if(account.getMoney() >= cash){
            double d = account.getMoney() - cash;
            System.out.println("取款成功,取款:"+cash+",余额"+d);
            account.setMoney(d);
        }else{
            System.out.println("取款失败,余额不足");
        }
    }

你可能感兴趣的:(技术类,后端,java,线程,多线程)