歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java多線程:一道阿裡面試題的分析與應對

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

日期:2017/3/1 9:47:00   编辑:Linux編程

引言: 通過多線程的面試題目分析,來深入理解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