世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程。
问答1、 下面的代码在绝大部分时间内都运行得很正常,请问在什么情况下会出现问题?问题的根源在哪里?
(必答-25%)
import java.util.LinkedList;
public class Stack {
LinkedList list = new LinkedList();
public synchronized void push(Object x) {
synchronized(list) {
list.addLast( x );
notify();
}
}
public synchronized Object pop()
throws Exception {
synchronized(list) {
if( list.size() <= 0 ) {
wait();
}
return list.removeLast();
}
}
}
上面这道题如果换一种形式,可能看起来更加容易:
import java.util.LinkedList;
public class Stack {
LinkedList list = new LinkedList();
public void push(Object x) {
synchronized(this) {
synchronized(list) {
list.addLast( x );
this.notify();
}
}
}
public Object pop()
throws Exception {
synchronized(this) {
synchronized(list) {
if( list.size() <= 0 ) {
this.wait();
}
return list.removeLast();
}
}
}
}
好了,现在比较明确了,接下来是如何解决。
要解决这个问题,就需要弄明白以下几点:
1》首先,弄明白有几个对象会被线程锁定?
回答:2个,一个是this,一个是list。
2》然后,弄明白wait()与notify针对的是哪个对象的锁?
回答:this,因为调用方式是o.wait(), o.nofity(), 这里隐藏的含义是 this.wait(), this.notify().
3》什么地方可能出问题?
回答:变化的地方容易出问题。那么哪里导致变化呢?当然是if 判断语句。
4》那么到底会出现什么问题呢?
我们需要分析一下,一旦list的size为0时,会调用this.wait()。这样this对象会释放掉他拥有的所有锁,说明方法中的synchronized会失效,但是list中的锁仍然存在。
那么情况就会变成这样:当前线程T1占有list对象的锁,但是不占有this对象的锁,这样就会出现多线抢this的锁,如果有其他线程T2抢到了this的锁,那么它很有可能执行push或pop操作,但是在执行操作进入方法时发现,T2需要获得list的锁才能继续进行下去,但是现在list的锁被T1占用,T2又获取不到。而T1想要执行push和pop又需要获取T2所占用的this的锁。
那么答案就出来了:T1想获取T2锁定的资源this,T2又想获取T1锁定的资源list,但是双发都不释放自己占有的锁 ,是什么问题呢?很明显,死锁。
5》根源在哪里?
根据前面的分析可知,根源在于不同的线程分别占有了this和list的锁,他们之间互相抢占对方正在占有资源,出现死循环,形成了死锁。
好了,到这里问题圆满解决。你的想法是什么样的呢?欢迎反馈。