多线程之争用条件

简介: 多线程之争用条件

什么是争用条件?


当多个线程同时访问同一数据(内存区域)时,每个线程都尝试操作改数据,从而导致数据被破坏(corrupted),这种现象称为争用条件。


下面用一个案例来说明。


案例:100个人之间进行相互转账,每个人都有一张银行卡,卡里1000元。在转账的时候都由银行的系统来操作。


银行系统:

package com.smxy.people;
/**
 * 
 * @Description 银行转账系统,所有的人都在这里进行转账业务
 * @author Bush罗
 * @date 2018年4月23日
 *
 */
public class MoneySystem {
  //每个人装钱的,类似于银行卡
  private int peopleMoney[];
  /**
   * 
   * @param num 有多少张卡
   * @param Money 每张卡多少钱
   */
  public MoneySystem(int num,int Money) {
    super();
    peopleMoney=new int[num];
    for(int i=0;i<num;i++){
      System.out.println(i);
      peopleMoney[i]=Money;
    }
  }
  /**
   * 
   * @param form 谁转的钱
   * @param to 转给谁
   * @param money 转的钱
   */
  public void transfer(int from,int to,int money){
    //如果自己的钱小于要转的钱就返回
    if(peopleMoney[from]<money){
      return;
    }
    peopleMoney[from]-=money;
    peopleMoney[to]+=money;
    System.out.print("people"+from+"转账给people"+to+""+money);
    System.out.println("所有的钱总和为"+allMoney());
  }
  /**
   * 计算所有人的钱
   * @return
   */
  public int allMoney(){
    int sum=0;
    for(int i=0;i<peopleMoney.length;i++){
      sum+=peopleMoney[i];
    }
    return sum;
  }
}

线程转账:

package com.smxy.people;
/**
 * 
 * @Description 线程转账
 * @author Bush罗
 * @date 2018年4月23日
 *
 */
public class MoneyTransferTask implements Runnable{
  //转账系统
  private MoneySystem moneySystem;
  //转账的人
  private int fromPeople;
  //单次转账的最大金额
  private int maxMoney;
  //最大休眠时间(毫秒)
  private int delay = 10;
  public MoneyTransferTask(MoneySystem moneySystem, int fromPeople,
      int maxMoney) {
    super();
    this.moneySystem = moneySystem;
    this.fromPeople = fromPeople;
    this.maxMoney = maxMoney;
  }
  @Override
  public void run() {
    // TODO Auto-generated method stub
    while(true){
      int toPeople = (int) (99*Math.random());//转给谁
      int money = (int)(maxMoney * Math.random());
      moneySystem.transfer(fromPeople, toPeople, money);
      try {
        Thread.sleep((int) (delay * Math.random()));
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }
}

测试代码:

package com.smxy.people;
/**
 * 
 * @Description 测试用例
 * @author Bush罗
 * @date 2018年4月23日
 *
 */
public class SystemTest {
  //总共有100个人在互相转账,每个人都是独立的线程
  private final static int Peoplesum=100;
  //每个人开始的总金额
  private final static int Money=1000;
  public static void main(String[] args) {
    //银行系统初始化
    MoneySystem moneySystem=new MoneySystem(Peoplesum,Money);
    //开启100个线程,进行互相转账
    for(int i=0;i<100;i++){
      MoneyTransferTask task=new MoneyTransferTask(moneySystem,i,Money);
      Thread thread=new Thread(task,"TransferThread_"+i);
      thread.start();
    }
  }
}

测试结果:

20180423133158546.png

 我们发现钱的总和变少了,那么为什么会产生这样的结果呢?


     我们知道在同一时间只能有一条线程在cup上运行,而线程之间的调度是通过分时和抢占来完成的。


     假设银行有1000块钱,如果在某一时间线程1获得cup的资源,获取银行目标数值1000,然后将转移的钱200添加,使得总的钱为1200,但是这是钱并没有写回银行系统,而是留在线程自己的内存之中,此时线程1的操作时间耗尽,线程调度切换线程2获得cup资源,线程2同样获取银行目标数值1000,同时将添加500,变为1500,然后将修改后的值写入银行系统。这时银行目标数值为1500,此时线程1又获得了cup资源继续执行它的上下文,把1200写入银行系统,这是银行的钱就变成1200了。两个线程由于访问了共享的数据区域,通过写的操作破坏了共享数据的值,使得我们产生了争用条件的情况。总共是添加了两次分别为200和500,但是最后只添加了500.损失了200.这就是为什么钱会少了的原因。


那么我们应该如何来避免这样的情况呢


通过互斥和同步就可以实现我们线程之间正确的交互,达到线程之间正确处理数据的要求。


互斥同步实现:通过增加一个锁来实现  synchronized+Object对象


银行改进代码:

         private final Object lockObj = new Object();
        /**
   * 
   * @param form
   *            谁转的钱
   * @param to
   *            转给谁
   * @param money
   *            转的钱
   */
  public void transfer(int from, int to, int money) {
    synchronized (lockObj) {
      // 如果自己的钱小于要转的钱就返回
      /*if (peopleMoney[from] < money) {
        return;
      }*/
      //while循环,保证条件不满足时任务都会被条件阻挡
      //而不是继续竞争CPU资源
       while (peopleMoney[from] < money){
         try {
          //条件不满足, 将当前线程放入Wait Set
          lockObj.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
       }
      peopleMoney[from] -= money;
      peopleMoney[to] += money;
      System.out.print(Thread.currentThread().getName() + " ");
      System.out.print("   people" + from + "转账给people" + to + "  "
          + money);
      System.out.println("   所有的钱总和为" + allMoney());
                        //唤醒所有在lockObj对象上等待的线程
                        lockObj.notifyAll(); 
                 }
  }

原来条件不满足是直接返回,但是线程返回后任然有机会获取cup资源从而再次要求加锁,而加锁是有开销的,会降低系统的性能。应该用while做判断,如果不满足条件就lockObj.wait();是线程进入一个等待的状态。任务结束后使用lockObj.notifyAll(); 告诉那些在等待的线程我已经执行完了,你们可以来做,然后他们又开始各凭本事争夺这个机会。如果是notify()方法就会唤醒在此对象监视器上等待的单个线程。


效果图:总金额不变


20180423143409817.png

Thread常用方法:

微信图片_20221210104352.png

相关文章
|
安全 Java
多线程02
多线程02
|
网络协议 安全
libev与多线程
libev与多线程
libev与多线程
|
9月前
|
Java API 调度
多线程 02
多线程 02
42 0
|
9月前
|
C#
[C#] 多线程的使用
[C#] 多线程的使用
57 0
|
9月前
|
安全 数据库 芯片
多线程的使用
多线程的使用
66 0
|
存储 安全 Java
今天聊聊多线程
今天聊聊多线程
52 0
|
调度 C++
多线程
多线程
|
安全 Linux C++
C++多线程实现
C++11支持语言级别的多线程编程,可以跨平台运行,支持windows/linux/mac等。
125 0
|
安全 C++
C++多线程(二)
C++多线程
164 0