分享

JAVA并发知识点-同步

 昵称63557093 2020-02-12

  1. 同步

  两个或两个以上的线程如何共享同一对数据的存取

  为了解决以上问题需要竞争条件

  1.1 竞争条件的一个例子

  为了避免多线程引起对共享数据的误操作,必须要同步存取

  模拟银行转账代码

  public void transfer(int from,int to,double amount){

  System.out.print(Thread.currentThread());

  accounts[from]-=amount;

  System.out.printf(%10.2f, from %d to %d,amount,from,to);

  System.out.printf(total balance: %10.2f%n,getTotalBalance;

  }

  这里是Runnable类的代码,run 方法不断从一个账户转到另一个账户

  Runnable r = ()-{

  try{

  while(true){

  int toAccount = (int)(bank.size()*Math.random);

  double amount = MAX_AMOUNT * Math.random();

  bank.transfer(fromAccount, toAccount, amount);

  Thread, sleep((int) (DELAY * Math.random()));

  }

  }

  catch(InterraputException e)

  {

  }

  }

  我们可以确定的是总额是不变的。但是结果确是这样

https://img1./5d2ca77000016ce907160304.jpg

  总额变化

https://img1./5d2ca7750001bc6107110588.jpg

  UnsynchBankTest

https://img4./5d2ca77a0001c73707110568.jpg

  Bank

https://img./5d2ca79e00013bcd07110427.jpg

  运行截图

  为什么会出现这种状况

  下面我们看一下详解

  两个线程同时更新一个银行账户的时候,会出现问题

  问题在于对金额的增加操作不是不可分割的原子操作

  amounts[to] 可以被处理成为以下操作

  1)将amounts[to] 加载到寄存器

  2)增加amount

  3)将结果写回amount

  // 假想第一个线程执行步骤1、2被剥夺了运行权,第二个线程被唤醒并修改了amounts之后第一个线程被唤醒,继续执行。这样第一个线程擦去第二个线程的修改

  无规律出错发生的事很可能是线程的原因

  上面代码出错的原因是transfer方法可能会被中断

  1.2 锁对象

  如果要想上述转移完成,就要让transfer方法不被中断。

  有两种方法防止代码块受并发干扰

  1. 通过ReentrantLock类

  private Lock myLock = new ReemtrantLock()

  try{

  do some work;

  }finally{

  myLock.unlock(); // make sure the lock is unlock;

  }

  // 这一结构确保任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句,其他线程调用lock时他们会被阻塞,知道第一个线程释放该锁对象

https://img4./5d2ca7a500011bdf05260483.jpg

  执行图

  但是线程在操作不同的bank实例的时候是不会相互阻塞的

  锁是可以重入的,线程可以重复获得已经持有的锁,一个被锁保护的代码可以调用另一个使用相同锁的方法

  transfer方法调用getTotalBalance方法也会封锁bankLock对象,此时bankLock对象持有数为2,当getTotalBalance退出持有计数才会变为1,当transfer方法退出后引用计数变为0,线程释放锁

https://img3./5d2ca7ab0001a85706970318.jpg

  常用锁方法

  1.3 条件对象

  通常情况下,线程进入临界区,却发现满足某一条件之后它才能执行。要使用一个条件对象来管理那些已经获得一个锁,但却不能做有用工作的线程

  我们来细化模拟银行的转账操作,避免没有足够的资金来转出

  不能使用以下的代码

  if (bank.getBalance(from) = amount)

  bank.transfer(from, to, amount) ;

  因为线程完全有可能成功完成测试之后中断

  if (bank.getBalance(from) = amount)

  // 可能在这里进行中断

  bank.transfer(from, to, amount) ;

  由于线程被中断了,可能线程再回来时已经余额不足,所以必须确保没有其他线程中断检查余额与转账操作。通过锁来保护检查余额与转账

https://img3./5d2ca7c40001158f04530390.jpg

  加锁控制

  线程获得了排他性访问,会一直拥有该锁。这是我们需要拥有条件锁的原因

  一个锁对象可以有一个或者多个相关的条件对象 newCondition获得条件对象

  习惯给条件对象命名所表达相关条件的名字

  class Bank {

  private Condition sufficientFunds;

  public Bank(){

  sufficientFunds = bankLock.newCondition();

  }

  }

  // 如果发现余额不足调用sufficientFunds.await()方法,线程阻塞释放该锁

  等待获得锁的线程或和调用await的方式是不同的

  线程调用await方法,进入该条件的等待集。当锁可用,该线程不能马上解除阻塞。阻塞状态,直到另一个线程调用统一条件上的signalAll方法

  signalAll方法激活因为这个条件而等待的线程,等待线程从等待集中移出,再次成为可运行的,调度器再次激活他们。它们从新竞争进入锁对象,一旦锁可用,他们中某个将从await调用返回,获得该锁从上次阻塞的地方继续运行

  一旦使用await,没法激活自身,需要寄希望与其他线程。否则永远不会运行

  注意死锁现象

  所有的线程不能均不能获得锁,造成死锁现象。程序被挂起

https://img./5d2ca7ca00014ee307110312.jpg

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多