記一個多執行緒鎖的bug
測試報了個問題,說我們的應用在退出之後,立即重新啟動會無效,必須等幾秒之後才能正常啟動。
追蹤程式碼之後發現問題出在對一個資源的獲取上。我們的應用在啟動的時候需要獲取一個裝置資源,但是這個裝置資源在程式退出之後幾秒才被釋放。導致在這幾秒內重新啟動程式的話就會獲取裝置資源失敗。
程式的大概邏輯如下:
- 獲取到裝置之後會起一個子執行緒,不斷從裝置中讀取資料,然後分發給上層
- 退出的時候需要關閉這個子執行緒,並且釋放裝置資源
- 為了防止正在從裝置中讀取的時候釋放裝置,需要加鎖保護
讀取資料的程式碼如下:
//在子執行緒中起一個死迴圈不斷從裝置讀取資料 while (true) { synchronized (mLock) { if (Thread.currentThread().isInterrupted()) { break; } // 從裝置中讀取資料並且分發 } }
退出的時候會退出子執行緒,並且釋放裝置資源:
synchronized (mLock) { mWorkThread.interrupt(); // 釋放裝置資源 }
乍一看是沒有問題的,但是實際加上列印的話會發現,子執行緒的while在做完一個迴圈釋放鎖之後,又立馬搶佔了鎖。導致在退出的時候遲遲不能獲取到鎖,一直阻塞在那。大概四五秒之後才拿到鎖,打斷子執行緒的死迴圈。
這位前輩不小心踩到了個坑: synchronized是非公平鎖。
也就是說,當兩個執行緒在競爭一個鎖的時候,系統並不會公平的將鎖你一次我一次的依次給兩個執行緒,而是隨機的給一個執行緒,並且比較大的概率是給上次獲得鎖的執行緒。這樣就導致while迴圈裡一直搶佔鎖資源,這樣退出的那個程式碼塊就一直被阻塞在那裡了。
暗暗欣喜自己之前的書沒有白看,立馬就想到關鍵原因。於是大手一揮,公平鎖伺候:
private final ReentrantLock mLock = new ReentrantLock(true); ... //在子執行緒中起一個死迴圈不斷從裝置讀取資料 while (true) { mLock.lock(); if (Thread.currentThread().isInterrupted()) { break; } // 從裝置中讀取資料並且分發 mLock.unlock(); } ... //退出的時候會退出子執行緒,並且釋放裝置資源 mLock.lock(); mWorkThread.interrupt(); // 釋放裝置資源 mLock.unlock();
驗證ok,繼續解別的bug去了。
吃過午飯,緩了緩神,詐屍般反應過來。我也被帶坑裡去了!
為什麼一定要用加鎖的方法?這鎖一加,程式碼的執行效率怎麼辦?
趕緊再改:
//在子執行緒中起一個死迴圈不斷從裝置讀取資料 while (true) { if (Thread.currentThread().isInterrupted()) { break; } // 從裝置中讀取資料並且分發 } // 釋放裝置資源 ... //退出的時候會退出子執行緒 mWorkThread.interrupt(); mWorkThread.join(); // 用join去等待子執行緒退出,子執行緒在跳出while迴圈的時候會自己是否資源
解bug的時候還是需要有自己的思路才行,不能被有問題的程式碼影響,要不然最後往往不能徹底解決問題,甚至有可能繼續埋坑。