傳說這是阿里的一道面試題: 也傳說發這道題出來的做者去了tmail。下面是關於題目的描述: 面試
這段代碼大多數狀況下運行正常,可是某些狀況下會出問題。何時會出現什麼問題?如何修正?可見博客 http://yueyemaitian.iteye.com/blog/1387901 數組
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);
- }
- }
- }
下面是關於這道題的分析:
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。
這個題目出的好,難道是阿里有人犯過這個錯誤!呵呵!
關於本題的討論若有任何紕漏,請你們及時指出呵!