全国服务热线:400-035-8011

位置:太原达内IT培训学校 > 学校动态 > 太原Java多线程之死锁的出现和解决方法

太原Java多线程之死锁的出现和解决方法

来源:太原达内IT培训学校时间:2022/2/9 16:16:28

  什么是死锁?
  死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不能正常运行.形象的说就是:一个宝藏需要两把钥匙来打开,同时间正好来了两个人,他们一人一把钥匙,但是双方都再等着对方能交出钥匙来打开宝藏,谁都没释放自己的那把钥匙.就这样这俩人一直僵持下去,直到开发人员发现这个局面。
  导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问.“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权.当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至个线程释放它加在对象上的锁。
  举个例子
  死锁的产生大部分都是在你不知情的时候.我们通过一个例子来看下什么是死锁。
  1、synchronized嵌套
  synchronized关键字可以增加多线程再访问到synchronized修饰的方法的时候增加了同步性.就是线程A访问到这个方法的时候线程B同时也来访问这个方法,这时线程B将进行阻塞,等待线程A执行完才可以去访问.这里就要用到synchronized所持有的同步锁.具体来看代码:
  /首先我们先定义两个final的对象锁.可以看做是共有的资源.
  final Object lockA = new Object();
  final Object lockB = new Object();
  //生产者A
  class ProductThreadA implements Runnable{
  @Override
  public void run() {
  //这里一定要让线程睡一会儿来模拟处理数据 ,要不然的话死锁的现象不会那么的明显.这里就是同步语句块里面,首先获得对象锁lockA,然后执行一些代码,随后我们需要对象锁lockB去执行另外一些代码.
  synchronized (lockA){
  //这里一个log日志
  Log.e("CHAO","ThreadA lock lockA");
  try {
  Thread.sleep(2000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  synchronized (lockB){
  //这里一个log日志
  Log.e("CHAO","ThreadA lock lockB");
  try {
  Thread.sleep(2000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  }
  }
  }
  //生产者B
  class ProductThreadB implements Runnable{
  //我们生产的顺序真好好生产者A相反,我们首先需要对象锁lockB,然后需要对象锁lockA.
  @Override
  public void run() {
  synchronized (lockB){
  //这里一个log日志
  Log.e("CHAO","ThreadB lock lockB");
  try {
  Thread.sleep(2000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  synchronized (lockA){
  //这里一个log日志
  Log.e("CHAO","ThreadB lock lockA");
  try {
  Thread.sleep(2000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  }
  }
  }
  //这里运行线程
  ProductThreadA productThreadA = new ProductThreadA();
  ProductThreadB productThreadB = new ProductThreadB();
  Thread threadA = new Thread(productThreadA);
  Thread threadB = new Thread(productThreadB);
  threadA.start();
  threadB.start();
  分析一下,当threadA开始执行run方法的时候,它会先持有对象锁localA,然后睡眠2秒,这时候threadB也开始执行run方法,它持有的是localB对象锁.当threadA运行到第二个同步方法的时候,发现localB的对象锁不能使用(threadB未释放localB锁),threadA就停在这里等待localB锁.随后threadB也执行到第二个同步方法,去访localA对象锁的时候发现localA还没有被释放(threadA未释放localA锁),threadB也停在这里等待localA锁释放.就这样两个线程都没办法继续执行下去,进入死锁的状态. 看下运行结果:
  10-20 14:54:39.940 18162-18178/? E/CHAO: ThreadA lock lockA
  10-20 14:54:39.940 18162-18179/? E/CHAO: ThreadB lock lockB
  当不会死锁的时候应该是打印四条log的,这里明显的出现了死锁的现象。
  死锁出现的原因
  当我们了解在什么情况下会产生死锁,以及什么是死锁的时候,我们在写代码的时候应该尽量的去避免这个误区.产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
  互斥条件:线程要求对所分配的资源进行排他性控制,即在一段时间内某 资源仅为一个进程所占有.此时若有其他进程请求该资源.则请求进程只能等待。
  不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
  请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。
  循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。
  解决死锁的方法
  1、解决死锁主要方法如下:
  (1)不考虑此问题,乐观的角度,鸵鸟算法
  (2)不让死锁发生:
  ①死锁预防。
  静态策略,通过设计合适的资源分配算法,不让死锁发生
  ②死锁避免
  动态策略,以不让死锁发生为目标,跟踪并评估资源分配过程,根据评估结果决策是否分配
  (3)让死锁发生:死锁的检测与解除
  2.死锁预防的具体方法:主要是破坏产生死锁的四个必要条件中的任何一个条件。
  (1)破坏“互斥使用/资源独占”条件
  使用资源转换技术,将独占资源变为共享资源
  (2)破坏“占有且等待”条件
  ①方案1:每个进程在运行前必须一次性的申请它所要求的全部资源,且仅当该进程所要的资源均可满足时才一次性的分配。
  资源利用率低,“饥饿”现象
  ②在允许进程动态申请资源的前提下规定,一个进行在申请新资源,且不能立即得到满足,必须释放已占有的全部资源。若需要再重新申请
  (3)破坏“不可选择”条件
  可以通过操作系统选择这一资源(根据进程的不同级)
  局限性:适用于状态易于保存和恢复的资源,如CPU(选择式的调度算法),内存(页面置换算法)
  (4)破坏“循环等待”条件
  通过定义资源类型的线性顺序实现
  方案:资源有序分配法。(如哲学家就餐问题)
  也就是把资源中所有的资源编号,进程在申请资源时,必须严格按照资源编号的递增次序进行,否则操作系统不予分配。(资源使用的频繁性?)
  3、死锁避免的方法
  (1)对不同分区进行不同的处理,左下可同时分配资源;右下区域可能会产生死锁,应该先分配P,释放后再分配Q;左上同理;右上区域时会产生死锁现象,不予分配资源。对可能发生死锁和可能产生死锁的不予分配资源,否则(安全状态)分配资源。
  (2)安全状态是指每个进程Pi以后还需要的资源量不超过系统当前剩余资源量与所有进程Pj当前占有资源量之和。
  4、死锁的检测与解除
  (1)允许死锁发生,但是操作系统会不断监视系统进展情况,判断死锁是否真的发生。(进程等待时检测,定时检测,系统资源利用率下降)
  (2)一旦死锁发生,采用专门的措施,解除死锁并以较小的代价恢复系统运行。
领取试听课
每天限量名额,先到先得

尊重原创文章,转载请注明出处与链接:http://www.peixun360.com/2387/news/480784/违者必究! 以上就是太原达内IT培训学校 小编为您整理 太原Java多线程之死锁的出现和解决方法的全部内容。

温馨提示:提交留言后老师会第一时间与您联系!热线电话:400-035-8011