
这是我最喜欢的java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码。仅仅知道线程的基本概念是远远不够的, 你必须知道如何处理死锁,竞态条件,内存冲突和线程安全等并发问题。最简单的解决死锁的问题是对死锁视而不见,首先要了解死锁发生的频率、系统因其它原因崩溃的频率、以及死锁有多严重,如果死锁平均每50年发生一次,而系统每个月会因硬件故障或操作系统错误等而崩溃一次,那么就可以不用不惜工本地去消除死锁。
Java线程死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。
假如线程 “A”获得了刀,而线程“B”获得了叉。线程“A”就会进入阻塞状态来等待获得叉,而线程“B”则阻塞来等待“A”所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免Java线程死锁问题:
让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。
srw锁可以使用上面两种模式中任意一种模式获得,通常读线程使用共享模式获得srw锁,写线程通常使用独占模式获得srw锁。位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,jvm就会把这个线程放到这个对象的琐池中。2、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,jvm就会把这个线程放到这个对象的琐池中。
将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。
最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现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关键字来声明变量,就可以告诉编译器在编译的时候,不需要通过预测变量值来优化这部分的代码。
sta 一个对象只能由一个线程访问(通过对象的接口指针调用其方法),其他线程不得访问这个对象,因此对于这个对象的所有调用都是同步了的,对象的状态(也就是对象的成员变量的值)肯定是正确变化的,不会出现线程访问冲突而导致对象状态错误。sta 一个对象只能由一个线程访问(通过对象的接口指针调用其方法),其他线程不得访问这个对象,因此对于这个对象的所有调用都是同步了的,对象的状态(也就是对象的成员变量的值。brian goetz对线程安全比较恰当的定义:当多个线程访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步多线程如何避免死锁,或者在调度方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。wait方法进入了阻塞队列,而上文讲过执行notify操作的线程与执行wait的线程是拥有同一个对象监视器,也就说wait方法执行之后,立刻释放掉锁,这样,另一个线程才能执行同步代码块,才能执行notify。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
=======================================================================
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
wait()释放对象上的锁,以便允许其他线程锁定和访问该对象。wait()释放对象上的锁以便允许其他线程锁定和访问该对象。wait() :释放对象上的锁以便允许其他线程锁定和访问该对象。
这个程序在某些应用中可能会导致程序锁死,比如要将连续、大量接收到的数据进行实时显示或存盘时会发生这种情况,原因是:串口通信线程每接收到一个字符,都要用sendmessage通知主线程,而sendmail是阻塞式的,如果此时主线程正在关闭串口,会用do...while循环连续向串口通信线程,直到串口通信线程中止为止,这个过程也是阻塞式的,此时主线程在不断判断串口通信线程是否中止,通信线程发来的sendmessage消息进行处理,而通信线程则在等待sendmessage的返回,不会对主线程发来的中止信号进行处理,从而导致死锁,进入漫长的超时等待状态。综合来看,线程 1.2 先执行了一段代码然后等待线程 1.3 完成执行多线程如何避免死锁,线程 1.3 完成执行后等待线程 1.2 执行另一段代码。加上了线程同步的代码,发现执行过程有乱序出现,我期望是pthread_create一个线程,然后main等待一个条件变量,这样能保证线程一个接一个创建,局部变量不会被改坏,实际执行结果是系统可以连续创建2个线程,然后进入死锁状态,不知道什么原因。
//代码一
Java代码 ![]()
classDeadlocker{

intfield_1;
privateObjectlock_1=newint[1];
intfield_2;
privateObjectlock_2=newint[1];
publicvoidmethod1(intvalue){
“synchronized”(lock_1){
“synchronized”(lock_2){
field_1=0;field_2=0;
}
}
}
publicvoidmethod2(intvalue){
“synchronized”(lock_2){
“synchronized”(lock_1){
field_1=0;field_2=0;
}
}
}
}
参考代码一,考虑下面的过程:
◆ 一个线程(ThreadA)调用method1()。
◆ ThreadA在lock_1上同步,但允许被抢先执行。
◆ 另一个线程(ThreadB)开始执行。
◆ ThreadB调用method2()。
当另一个线程调用pthread_cond_signal()唤醒之前需要先把lock给释放了,因为调用pthread_cond_signal()后,消费进程需要重新通过pthread_cond_wait()这个方法获得lock,然后执行逻辑处理代码,最后消费进程释放lock.。threada所拥有,那麽threada执行waitforsingleobject()时,并不会停下来,而会立即。 start 规则:如果线程a执行操作threadb.start (启动线程b), 那么a线程的threadb.start 操作happens- before于线程b中的任意操作。
◆ 现在,ThreadB阻塞,因为它在等待ThreadA释放lock_1。
◆ 现在轮到ThreadA继续执行。ThreadA试图获得lock_2,但不能成功,因为lock_2已经被ThreadB占有了。
◆ ThreadA和ThreadB都被阻塞,程序死锁。
13.compuware devpartner java edition-包含java内存检测,代码覆盖率测试,代码性能测试,线程死锁,分布式应用等几大功能模块。18. compuwaredevpartner java edition-包含java内存检测,代码覆盖率测试,代码性能测试,线程死锁,分布式应用等几大功能模块。49:根据线程安全的相关知识,分析以下代码,当调用test方法时i>10时是否会引起死锁。
隐性死锁
上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。无论是强行停止应用锁,清除应用锁数据还是直接禁止应用锁悬浮窗权限都可以干掉这层安全措施。①、死锁:线程a占有资源a,线程b占有资源b,线程a申请占有资源b,同时要求占有资源b之后才释放资源a,而线程b申请占有资源a,同时要求占有资源a之后才释放资源b,这样两个线程互相永久等待对方释放资源,这就是死锁。
加锁次序
当多个并发的线程分别试图同时占有两个锁时,会出现加锁次序冲突的情形。如果一个线程占有了另一个线程必需的锁,就有可能出现死锁。考虑下面的情形,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代码 ![]()
publicclassqueue{
staticjava.lang.ObjectqueueLock_;
Producerproducer_;
Consumerconsumer_;
publicclassProducer{
voidproduce(){
while(!done){
“synchronized”(queueLock_){

produceItemAndAddItToQueue();
“synchronized”(consumer_){
consumer_.notify();
}
}
}
}
publicclassConsumer{
consume(){
while(!done){
“synchronized”(queueLock_){
“synchronized”(consumer_){
consumer_.wait();
}
removeItemFromQueueAndProcessIt();
}
}
}
}
}
}
Java代码 ![]()
publicclassqueue{
staticjava.lang.ObjectqueueLock_;
Producerproducer_;
Consumerconsumer_;
publicclassProducer{

voidproduce(){
while(!done){
“synchronized”(queueLock_){
produceItemAndAddItToQueue();
“synchronized”(consumer_){
consumer_.notify();
}
}
}
}
publicclassConsumer{
consume(){
while(!done){
“synchronized”(queueLock_){
“synchronized”(consumer_){
consumer_.wait();
}
removeItemFromQueueAndProcessIt();
}
}
}
}
}
}
为了实现等待/通知机制,我们还必须通过锁对象去创建一个条件对象condition,然后通过锁对象的await()和signalall()方法去实现等待以及通知操作。mutex互斥锁必须是普通锁(pthread_mutex_timed_np)或者适应锁 (pthread_mutex_adaptive_np),且在调用pthread_cond_wait()前必须由本线程加锁 (pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并程挂起进入等待前解锁。mutex互斥锁必须是普通锁(pthread_mutex_timed_np)或者适应锁(pthread_mutex_adaptive_np),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并程挂起进入等待前解锁。
在等待时占有的锁是一种隐性的死锁,这是因为事情可能按照比较理想的情况发展—Producer线程不需要被Consumer占据的锁。尽管如此,除非有绝对可靠的理由肯定Producer线程永远不需要该锁,否则这种编程方式仍是不安全的。有时“占有并等待”还可能引发一连串的线程等待,例如,线程A占有线程B需要的锁并等待,而线程B又占有线程C需要的锁并等待等。
要改正代码二的错误,只需修改Consumer类,把wait()移出“synchronized”()即可。
因此避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。
此外,Thread类的suspend()方法也很容易导致死锁,因此这个方法已经被废弃了.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-115885-1.html
美国永远是世界爱好和平人民的公敌
锅炉