java-多线程-一道阿里面试题分析

传说这是阿里的一道面试题: 也传说发这道题出来的做者去了tmail。下面是关于题目的描述: 面试

     这段代码大多数状况下运行正常,可是某些状况下会出问题。何时会出现什么问题?如何修正?可见博客 http://yueyemaitian.iteye.com/blog/1387901  数组

Java代码   收藏代码
  1. public class MyStack {  
  2.     private List<String> list = new ArrayList<String>();  
  3.   
  4.     public synchronized void push(String value) {  
  5.         synchronized (this) {  
  6.             list.add(value);  
  7.             notify();  
  8.         }  
  9.     }  
  10.   
  11.     public synchronized String pop() throws InterruptedException {  
  12.         synchronized (this) {  
  13.             if (list.size() <= 0) {  
  14.                 wait();  
  15.             }  
  16.             return list.remove(list.size() - 1);  
  17.         }  
  18.     }  
  19. }  

下面是关于这道题的分析:


   list.remove(list.size() - 1);这句代码有可能引起数组下标越界
缘由:
假设其中一种情形呵!出问题的情形可能不少,但原理都差很少。下面的标号表明程序时序的前后顺序。
 1,初始化时list的值为0,而后线程1调用了pop,因而被wait了,而后释放了锁。
 2,线程2调用push,在notify以前有线程3调用pop(记住这时候线程1尚未被唤醒,还在wait住),此时线程3会由于等待锁而挂起,或自旋,反正就是在等待锁可用。
 3,而后线程2继续往下执行,notify被执行(但这时候线程1是不会唤醒的,由于锁还在线程2占用),线程2退出push方法,释放内置锁,此时,线程1和线程3都在内置锁等待队列里面。因为synchronized是无法保证线程竞争的公平性,因此线程1和线程3均可能获得锁。
 4,假设线程1竞争到了锁,不会出问题,正常去除list值,而后remove,执行完后线程3执行,一样被wait住。
 5,假设线程3竞争到了锁,问题来了,线程3会判断到list的size不为0,因而remove,因此list的size就为0了,而后线程 3释放锁,这时候,线程1就获得锁,因而从wait中醒来,继续执行,而后直接调用list的remove,因为list的size=0,那么remove(-1),越界错误就产生了。


   还有同窗说两个线程都在wait处等候也会出问题,其实不会出问题的,由于是调用的notify而不是notifyAll,若是是调用notifyAll那么也会出一样的问题。


  至于改进:
  看到这个题目我就很纳闷,为何要用双重锁,好像没有必要双重锁。我第一眼看到双重锁的时候就在想,出题者是否是在模拟一个套管死锁,我也确实为找这个死锁付出了一些时间。可是这个双重检查都是可重入的锁,都是对于this对象上的锁。因此不存在套管死锁。
  改进1,——最小代码改动,就在remove以前再检查list.size==0
  改进2,——去掉push和pop方法内的第二重锁检查,我确实没有发现这个锁会有什么用,反而耗性能。 固然这里仍是要有方案1的判断(谢谢一楼提醒)
  改进3,——从新设计,若是是我来设计这么一个生产者,消费者模式。我更愿意用LinkedBlockingQueue,它有take方法阻塞消费者直到队列可用。并且还有offer方法阻塞生产者直到队列能够插入,能够有效的阻止OOM。

 
  这个题目出的好,难道是阿里有人犯过这个错误!呵呵!

  关于本题的讨论若有任何纰漏,请你们及时指出呵!
相关文章
相关标签/搜索