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

首先,来看看这个面试题目吧。

题目来源:   http://www.linuxidc.com/Linux/2014-03/98715.htm

 public class MyStack {  
        private List<String> list = new ArrayList<String>();  
      
        public synchronized void push(String value) {  
            synchronized (this) {  
                list.add(value);  
                notify();  
            }  
        }  
      
        public synchronized String pop() throws InterruptedException {  
            synchronized (this) {  
                if (list.size() <= 0) {  
                    wait();  
                }  
                return list.remove(list.size() - 1);  
            }  
        }  
    }

问题:  这段代码大多数情况下运行正常,但是某些情况下会出问题。什么时候会出现什么问题?如何修正?

可以看出,MyStack主要实现入栈出栈功能,ArrayList不是线程安全的类,因此程序中用synchronized关键字来保证线程安全。大多数情况下,都能正确运行,但是在特殊情况下会出现一些意外。

       tips:从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

case1:删除不存在的元素

      假设现在有三个线程A、B、C,其中A用于添加元素,B、C用于删除元素。

      某时刻,栈为空,

      step1、线程B运行,获取锁,list.size()=0,进入wait(),wait状态下会释放当前锁

      step2、线程A运行,获取锁,添加元素,执行list.add(value),此时list.size()=1,注意:在A执行notify()之前,线程C启动,发现其他线程已经拥有对象锁,因此进入阻塞状态,等待锁

     step3、线程A执行notify(),试图唤醒等待中的线程B,但是但是但是,如果此时C获取了对象锁,那么将优先执行,那么C判断list.size()=1,直接删除元素,然后释放对象锁

     step4、wait状态下的B获取对象锁,直接执行list.remove(list.size()-1),发生错误!!!

    解决办法: 使用可同步的数据结构来存放数据,比如LinkedBlockingQueue之类。由这些同步的数据结构来完成繁琐的同步操作。

case2:虚假唤醒

    虚假唤醒就是一些obj.wait()会在除了obj.notify()和obj.notifyAll()的其他情况被唤醒,而此时是不应该唤醒的。

    解决的办法是基于while来反复判断进入正常操作的临界条件是否满足: (将if换成while)

        synchronized (obj) {  
            while (<condition does not hold>)  
                obj.wait();  
            ... // Perform action appropriate to condition  
        } 


posted @ 2015-08-12 16:43  懒人部落  阅读(1246)  评论(0编辑  收藏  举报