1. 程式人生 > >一道阿里多執行緒面試題分析

一道阿里多執行緒面試題分析

首先,來看看這個面試題目吧。

題目來源:   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  
        }