阿里笔试测评,这道多线程的题你如何解答?

98 阅读2分钟

世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程。

问答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的锁,他们之间互相抢占对方正在占有资源,出现死循环,形成了死锁。

好了,到这里问题圆满解决。你的想法是什么样的呢?欢迎反馈。