歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java多線程:一道阿裡面試題的分析與應對

引言: 通過多線程的面試題目分析,來深入理解Java線程的狀態轉變過程。

最近在學習Java多線程設計的時候,在網上看到一個面試題目的討論,雖然樓主所說有些道理,但感覺還是有些問題,故此在和同事討論以後還是有了若干收獲,在此略作總結。

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

    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); 
            } 
        } 
    } 

問題:  這段代碼大多數情況下運行正常,但是某些情況下會出問題。什麼時候會出現什麼問題?如何修正?

代碼分析:

從整體上,在並發狀態下,push和pop都使用了synchronized的鎖,來實現同步,同步的數據對象是基於List的數據;大部分情況下是可以正常工作的。

問題描述:

狀況1:

1.  假設有三個線程: A,B,C.  A 負責放入數據到list,就是調用push操作, B,C分別執行Pop操作,移除數據。

2.  首先B先執行,於pop中的wait()方法處,進入waiting狀態,進入等待隊列,釋放鎖。

3.  A首先執行放入數據push操作到List,在調用notify()之前; 同時C執行pop(),由於synchronized,被阻塞,進入Blocked狀態,放入基於鎖的等待隊列。注意,這裡的隊列和2中的waiting等待隊列是兩個不同的隊列。

4.    A線程調用notify(),喚醒等待中的線程A。

5.    如果此時, C獲取到基於對象的鎖,則優先執行,執行pop方法,獲取數據,從list移除一個元素。

6.  然後,A獲取到競爭鎖,A中調用list.remove(list.size() - 1),則會報數據越界exception。

狀況2:

1.  相同於狀況1

2.  B、C都處於等待waiting狀態,釋放鎖。等待notify()、notifyAll()操作的喚醒。

3.  存在被虛假喚醒的可能。

何為虛假喚醒?

虛假喚醒就是一些obj.wait()會在除了obj.notify()和obj.notifyAll()的其他情況被喚醒,而此時是不應該喚醒的。

解決的辦法是基於while來反復判斷進入正常操作的臨界條件是否滿足:

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

如何修復問題?

#1.  使用可同步的數據結構來存放數據,比如LinkedBlockingQueue之類。由這些同步的數據結構來完成繁瑣的同步操作。

#2.  雙層的synchronized使用沒有意義,保留外層即可。

#3.  將if替換為while,解決虛假喚醒的問題。

Copyright © Linux教程網 All Rights Reserved