`
lvwenwen
  • 浏览: 956073 次
  • 性别: Icon_minigender_1
  • 来自: 魔都
社区版块
存档分类
最新评论

java 死锁及解决

阅读更多

url:http://leowzy.iteye.com/blog/740859

java线程死锁如何避免这一悲剧  Java线程死锁需要如何解决,这个问题一直在我们不断的使用中需要只有不断的关键。不幸的是,使用上锁会带来其他问题。让我们来看一些常见问题以及相应的解决方法:

  Java线程死锁

  Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。

  假如线程 “A”获得了刀,而线程“B”获得了叉。线程“A”就会进入阻塞状态来等待获得叉,而线程“B”则阻塞来等待“A”所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免Java线程死锁问题:

  让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。

 

  将多个锁组成一组并放到同一个锁下。前面Java线程死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。

 

  将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。

 

  最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现Java线程死锁的问题。

 

  Volatile 变量,volatile 关键字是 Java 语言为优化编译器设计的。以下面的代码为例:

  1.class VolatileTest {

  2.public void foo() {

  3.boolean flag = false;

  4.if(flag) {

  5.//this could happen

  6.}

  7.}

  8.}

  一个优化的编译器可能会判断出if部分的语句永远不会被执行,就根本不会编译这部分的代码。如果这个类被多线程访问, flag被前面某个线程设置之后,在它被if语句测试之前,可以被其他线程重新设置。用volatile关键字来声明变量,就可以告诉编译器在编译的时候,不需要通过预测变量值来优化这部分的代码。

  无法访问的Java线程死锁有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。在 Java 编程中IO就是这类问题最好的例子。当线程因为对象内的IO调用而阻塞时,此对象应当仍能被其他线程访问。该对象通常有责任取消这个阻塞的IO操作。造成阻塞调用的线程常常会令同步任务失败。如果该对象的其他方法也是同步的,当线程被阻塞时,此对象也就相当于被冷冻住了。

  其他的线程由于不能获得对象的Java线程死锁,就不能给此对象发消息(例如,取消 IO 操作)。必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方法。尽管这种方法需要花费一些注意力来保证结果代码安全运行,但它允许在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。

 

=======================================================================

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 

  导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性的访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。 

  由于这个原因,在使用“synchronized”关键词时,很容易出现两个线程互相等待对方做出某个动作的情形。代码一是一个导致死锁的简单例子。 

//代码一

Java代码 复制代码
  1. class Deadlocker {   
  2.  int field_1;   
  3.  private Object lock_1 = new int[1];   
  4.  int field_2;   
  5.  private Object lock_2 = new int[1];   
  6.   
  7.  public void method1(int value) {   
  8.   “synchronized” (lock_1) {   
  9.    “synchronized” (lock_2) {   
  10.     field_1 = 0; field_2 = 0;   
  11.    }   
  12.   }   
  13.  }   
  14.   
  15.  public void method2(int value) {   
  16.   “synchronized” (lock_2) {   
  17.    “synchronized” (lock_1) {   
  18.     field_1 = 0; field_2 = 0;   
  19.    }   
  20.   }   
  21.  }   
  22. }   
  23.    
Java代码  收藏代码
  1. class Deadlocker {  
  2.  int field_1;  
  3.  private Object lock_1 = new int[1];  
  4.  int field_2;  
  5.  private Object lock_2 = new int[1];  
  6.   
  7.  public void method1(int value) {  
  8.   “synchronized” (lock_1) {  
  9.    “synchronized” (lock_2) {  
  10.     field_1 = 0; field_2 = 0;  
  11.    }  
  12.   }  
  13.  }  
  14.   
  15.  public void method2(int value) {  
  16.   “synchronized” (lock_2) {  
  17.    “synchronized” (lock_1) {  
  18.     field_1 = 0; field_2 = 0;  
  19.    }  
  20.   }  
  21.  }  
  22. }  
  23.    



  参考代码一,考虑下面的过程: 

  ◆ 一个线程(ThreadA)调用method1()。 

  ◆ ThreadA在lock_1上同步,但允许被抢先执行。 

  ◆ 另一个线程(ThreadB)开始执行。 

  ◆ ThreadB调用method2()。 

  ◆ ThreadB获得lock_2,继续执行,企图获得lock_1。但ThreadB不能获得lock_1,因为ThreadA占有lock_1。 

  ◆ 现在,ThreadB阻塞,因为它在等待ThreadA释放lock_1。 

  ◆ 现在轮到ThreadA继续执行。ThreadA试图获得lock_2,但不能成功,因为lock_2已经被ThreadB占有了。 

  ◆ ThreadA和ThreadB都被阻塞,程序死锁。 

  当然,大多数的死锁不会这么显而易见,需要仔细分析代码才能看出,对于规模较大的多线程程序来说尤其如此。好的线程分析工具,例如JProbe Threadalyzer能够分析死锁并指出产生问题的代码位置。 

  隐性死锁 

  隐性死锁由于不规范的编程方式引起,但不一定每次测试运行时都会出现程序死锁的情形。由于这个原因,一些隐性死锁可能要到应用正式发布之后才会被发现,因此它的危害性比普通死锁更大。下面介绍两种导致隐性死锁的情况:加锁次序和占有并等待。 

  加锁次序 

  当多个并发的线程分别试图同时占有两个锁时,会出现加锁次序冲突的情形。如果一个线程占有了另一个线程必需的锁,就有可能出现死锁。考虑下面的情形,ThreadA和ThreadB两个线程分别需要同时拥有lock_1、lock_2两个锁,加锁过程可能如下: 

  ◆ ThreadA获得lock_1; 

  ◆ ThreadA被抢占,VM调度程序转到ThreadB; 

  ◆ ThreadB获得lock_2; 

  ◆ ThreadB被抢占,VM调度程序转到ThreadA; 

  ◆ ThreadA试图获得lock_2,但lock_2被ThreadB占有,所以ThreadA阻塞; 

  ◆ 调度程序转到ThreadB; 

  ◆ ThreadB试图获得lock_1,但lock_1被ThreadA占有,所以ThreadB阻塞; 

  ◆ ThreadA和ThreadB死锁。 

  必须指出的是,在代码丝毫不做变动的情况下,有些时候上述死锁过程不会出现,VM调度程序可能让其中一个线程同时获得lock_1和lock_2两个锁,即线程获取两个锁的过程没有被中断。在这种情形下,常规的死锁检测很难确定错误所在。 

  占有并等待 

  如果一个线程获得了一个锁之后还要等待来自另一个线程的通知,可能出现另一种隐性死锁,考虑代码二。 

//代码二

Java代码 复制代码
  1. public class queue {   
  2.  static java.lang.Object queueLock_;   
  3.  Producer producer_;   
  4.  Consumer consumer_;   
  5.   
  6.  public class Producer {   
  7.   void produce() {   
  8.    while (!done) {   
  9.     “synchronized” (queueLock_) {   
  10.      produceItemAndAddItToQueue();   
  11.      “synchronized” (consumer_) {   
  12.       consumer_.notify();   
  13.      }   
  14.     }   
  15.    }   
  16.   }   
  17.   
  18.   public class Consumer {   
  19.    consume() {   
  20.     while (!done) {   
  21.      “synchronized” (queueLock_) {   
  22.       “synchronized” (consumer_) {   
  23.        consumer_.wait();   
  24.       }   
  25.       removeItemFromQueueAndProcessIt();   
  26.      }   
  27.     }   
  28.    }   
  29.   }   
  30.  }   
  31. }   
  32.    
Java代码  收藏代码
  1. public class queue {  
  2.  static java.lang.Object queueLock_;  
  3.  Producer producer_;  
  4.  Consumer consumer_;  
  5.   
  6.  public class Producer {  
  7.   void produce() {  
  8.    while (!done) {  
  9.     “synchronized” (queueLock_) {  
  10.      produceItemAndAddItToQueue();  
  11.      “synchronized” (consumer_) {  
  12.       consumer_.notify();  
  13.      }  
  14.     }  
  15.    }  
  16.   }  
  17.   
  18.   public class Consumer {  
  19.    consume() {  
  20.     while (!done) {  
  21.      “synchronized” (queueLock_) {  
  22.       “synchronized” (consumer_) {  
  23.        consumer_.wait();  
  24.       }  
  25.       removeItemFromQueueAndProcessIt();  
  26.      }  
  27.     }  
  28.    }  
  29.   }  
  30.  }  
  31. }  
  32.    




  在代码二中,Producer向队列加入一项新的内容后通知Consumer,以便它处理新的内容。问题在于,Consumer可能保持加在队列上的锁,阻止Producer访问队列,甚至在Consumer等待Producer的通知时也会继续保持锁。这样,由于Producer不能向队列添加新的内容,而Consumer却在等待Producer加入新内容的通知,结果就导致了死锁。 

  在等待时占有的锁是一种隐性的死锁,这是因为事情可能按照比较理想的情况发展—Producer线程不需要被Consumer占据的锁。尽管如此,除非有绝对可靠的理由肯定Producer线程永远不需要该锁,否则这种编程方式仍是不安全的。有时“占有并等待”还可能引发一连串的线程等待,例如,线程A占有线程B需要的锁并等待,而线程B又占有线程C需要的锁并等待等。 

  要改正代码二的错误,只需修改Consumer类,把wait()移出“synchronized”()即可。 

  因此避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。 
  此外,Thread类的suspend()方法也很容易导致死锁,因此这个方法已经被废弃了.

分享到:
评论

相关推荐

    java模拟线程死锁

    Java 模拟线程死锁 线程死锁 在 Java 中,线程死锁(Deadlock)是一种特殊的情况,发生在两个或多个线程之间的互相等待对方释放资源的状态。这种情况下,各个线程都在等待其他线程释放资源,而自己也占用着其他...

    JAVA死锁and生产者消费者问题

    在Java编程中,死锁和生产者消费者问题是多线程编程中的两个重要概念,它们涉及到并发执行和资源管理。理解并正确处理这些问题对于构建高效、可靠的多线程应用至关重要。 死锁是指两个或多个线程在执行过程中,因...

    Java多线程程序死锁检查 JCarder

    总之,理解和掌握Java多线程中的死锁问题及其解决方案是每个Java开发者必备的技能。通过使用JCarder这样的工具,我们可以更加高效地调试和优化多线程程序,确保其在高并发环境下的正确性和稳定性。

    Java源码房门终于被打开了(解决死锁的方法).rar

    在“房门终于被打开了(解决死锁的方法)”这个主题中,可能详细分析了实际案例,演示了如何通过上述策略解决Java死锁问题。通过阅读"说明.txt"和"房门终于被打开了(解决死锁的方法)"这两个文件,我们可以更深入地...

    Java解决死锁问题eclipse代码版

    本文将深入探讨如何使用`synchronized`来解决Java中的死锁问题。 首先,我们需要理解死锁的四个必要条件: 1. 互斥条件:至少有一个资源必须在任何时候只能由一个线程使用。 2. 请求与保持条件:一个线程因请求被...

    java死锁问题

    Java死锁问题是一个在多线程编程中常见的复杂情况,它发生在两个或多个线程相互等待对方释放资源,导致它们都无法继续执行的情况。理解并解决死锁是Java开发者必须掌握的关键技能之一,尤其是在构建高并发、多线程...

    Java 实例 - 死锁及解决方法源代码+详细指导教程.zip

    本教程将深入探讨Java中的死锁问题及其解决策略,通过实例源代码帮助你更好地理解和避免这种情况。 1. **死锁定义**: 死锁是指两个或多个并发执行的线程在执行过程中,因争夺资源而造成的一种相互等待的现象,若...

    Java线程死锁实例及解决方法

    Java线程死锁实例及解决方法 Java线程死锁是一种特殊的线程状态,发生死锁时,多个线程会相互等待,无法继续执行,直到外部干预。下面是Java线程死锁实例及解决方法的详细介绍。 一、死锁的定义 所谓死锁是指多个...

    sqlite死锁datebaselock解决方案

    4. **死锁检测与恢复**:SQLite内建了死锁检测机制,当检测到死锁时,会回滚其中一个事务以解除死锁。但也可以在应用程序层面主动检测和处理死锁。 5. **使用WAL模式**:Write-Ahead Logging(预写日志)模式能有效...

    Java 线程死锁的问题解决办法

    Java 线程死锁的问题解决办法 Java 线程死锁是指两个或两个以上的线程在执行过程中,相互等待对方释放资源,从而造成的僵持状态。在 Java 中,线程死锁的问题解决办法是非常重要的,下面我们将深入探讨 Java 线程...

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

    Java多线程之死锁的出现和解决方法 死锁是指多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。死锁的出现是由于不适当地运用“synchronized”...

    java 门锁终于被打开了(解决死锁)

    在Java编程语言中,"门锁"通常是指同步...总之,理解并掌握Java中的门锁机制和死锁解决方案对于编写高效、稳定的多线程程序至关重要。开发者应始终警惕潜在的死锁风险,遵循良好的并发编程实践,以保证程序的正常运行。

    银行家避免死锁算法模拟实现Java版

    银行家算法是解决死锁问题的一种有效策略,它由艾兹格·迪杰斯特拉提出,主要用于预防死锁的发生。本文将深入探讨银行家算法的基本原理,并以Java语言实现其模拟过程。 银行家算法的核心思想是预先分配并管理系统...

    死锁检测:死锁检测的Java实现

    在操作系统领域,死锁是一个非常重要的概念,它发生在两个或多个进程之间,每个进程都在等待其他...Java提供了一些工具和接口帮助我们识别和处理死锁,通过合理的设计和编程实践,我们可以有效地预防和解决死锁问题。

    SQL死锁,删除失败解决办法

    SQL死锁是一种常见的数据库性能问题,了解其原因及处理方法对于DBA和开发人员来说非常重要。通过上述介绍的方法和技术,可以有效地检测和解决死锁问题,从而保证系统的稳定性和高效性。此外,还可以通过调整数据库...

    死锁算法 Java实现 操作系统

    本教程将通过Java实现死锁的模拟,帮助学习者理解这一复杂的概念,并提供一个简单的、易于理解的解决方案。 首先,我们需要了解死锁的四个必要条件: 1. **互斥条件**:资源必须被单个进程独占,即在同一时刻,...

    Java中的死锁:理解、避免与解决策略

    死锁是Java并发编程中的一个常见问题,它需要开发者有深入的理解并采取有效的策略来避免。通过破坏死锁产生的条件、使用合适的同步机制和工具,可以有效地预防和解决死锁问题。在实际开发中,应尽量避免不必要的同步...

    java线程死锁实例

    理解并解决Java线程死锁对于优化并发程序性能至关重要。 1. **互斥条件**:线程对所占有的资源具有独占性,即在同一时刻,只有一个线程可以使用该资源。例如,两个线程试图同时写入同一份文件,这是不允许的。 2. ...

    Java中的并发死锁问题:检测、预防与解决策略

    在Java中,死锁的产生通常需要满足四个条件:互斥使用、不可抢占、请求和保持、循环等待。理解这些条件有助于我们采取相应的策略来预防和解决死锁问题。 死锁是并发编程中的一个常见问题,它可能导致程序冻结并变得...

    如何通过编程发现Java死锁

    尽管出现了一些静态分析库可以帮助我们发现可能出现的死锁,我们还是有必要在运行时检测到死锁,并且得到有用的信息,以便我们解决这个问题或者重启程序,或者做些其他的事情。  在编程中使用ThreadMXBean类来检测...

Global site tag (gtag.js) - Google Analytics