給定一個理論系統,如果在本地系統中找不到檔案,則從 Web 下載檔案并假設:
- 下載機制和從/在快取中(本地檔案系統)的檢索/放置已經得到處理。
- 每個 URL 的單執行緒和單個請求。
我寫了一個方法,使用 getFileFromLocalFS() 和 getFileFromWeb() 來實作一個簡化的快取邏輯:
public InputStream getFile(String url) { // #1
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
return retStream;
}
然后需要改進這個示意性解決方案以適應從同一 URL 下載的并發請求......并將實際的“從 Web”限制為單個下載(即所有其他請求將從本地檔案系統獲取它)。所以,我同步了整個方法:
public synchronized InputStream getFile(String url) { // #2
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
return retStream;
}
這基本上滿足了請求,但存在性能問題,因為它會阻止整個方法由另一個執行緒運行,直到它完成。也就是說,即使可以從本地 FS 獲取檔案,getFileFromLocalFS(url)當該方法由另一個執行緒運行時也無法訪問。
我的面試官建議的性能改進是同步getFileFromLocalFS(url)塊:
public synchronized InputStream getFile(String url) { // #3
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
synchronized (this) {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
}
return retStream;
}
我說“很好,但是要使優化作業,需要洗掉方法同步”,即:
public InputStream getFile(String url) { // #4
InputStream retStream = getFileFromLocalFS(url);
if (retStream != null) {
return retStream;
}
else {
synchronized (this) {
retStream = getFileFromLocalFS(url);
if (retStream == null) {
return getFileFromWeb(url);
}
}
}
return retStream;
}
面試官不同意,堅持讓兩者都 synchronized留在原地。
哪一個在并發環境中表現更好?#3 還是 #4?為什么?
uj5u.com熱心網友回復:
似乎正在進行Cargo Cult Programming。第三種變體類似于雙重檢查鎖定,但沒有得到重點。但更引人注目的是,第一個變體也類似于雙重檢查鎖定,getFileFromLocalFS(url)無故呼叫兩次。這就是起點……
你懷疑面試官的“進步”是對的。嵌套同步沒有任何作用,在最好的情況下,JVM 的優化器將能夠消除它。
但是,不推薦這兩種解決方案。這里有一個更深層次的問題。首先,在討論性能之前,我們應該關注正確性。
顯然,getFileFromLocalFS(url)如果它存在,應該找到一個本地副本,而不是創建它。如果沒有人創建這樣的副本,并且如果此方法不執行快取嘗試,那么關于重疊快取嘗試的整個問題將毫無意義,這將毫無意義。因此,唯一涉及的其他方法getFileFromWeb(url)必須是創建本地副本的方法。
這意味著這兩種方法之間存在隱藏協議。getFileFromLocalFS(url)不知何故,如何獲取由getFileFromWeb(url).
這引發了一些問題,例如,如果一個執行緒正在getFileFromWeb(url)創建快取版本而另一個執行緒呼叫getFileFromLocalFS(url)相同的 url,會發生什么?它是否將輸入流回傳到仍然不完整的本地檔案?有兩種可能:
這個問題已經以某種方式解決了。這意味著在幕后已經有一些執行緒安全的結構來防止這種競爭條件,因此,它也應該首先用于
getFileFromWeb(url)解決多次快取嘗試的問題。或者這個問題沒有解決,出發點是一個損壞的方法,所以首要任務應該是解決這個問題,而不是試圖提高性能。如果我們真的嘗試在兩個方法之外修復這個問題,即在 中
getFile,只有第二個變體是正確的,在內部呼叫這兩個方法synchronized。
在任何一種情況下,該getFile方法都是解決問題的錯誤位置。并發問題應該通過兩種方法之間已經存在的隱藏協議來解決。
例如:
如果這兩種方法使用從 url 到本地檔案的映射,它們應該使用并發映射及其原子更新操作來解決并發問題。
如果這兩種方法使用映射方案將 url 轉換為副本應該在的本地路徑,并且該
getFileFromLocalFS(url)方法僅檢查檔案是否存在,則該getFileFromWeb(url)方法應創建具有CREATE_NEW原子檢查存在選項的檔案如果不存在,則創建,以及檔案鎖定以防止其他執行緒在快取仍在進行時讀取。
uj5u.com熱心網友回復:
它們基本上都是雙重檢查鎖定模式的(有些模糊的)版本。但是鑒于呼叫getFileFromLocalFS(url)本身可能代價高昂(它是一個 I/O 呼叫,它總是比記憶體中的操作更昂貴),這種形式的模式并不理想。理想情況下,雙重檢查鎖定的實作應該盡可能“緊密” - 這意味著兩個鎖應該盡可能靠近彼此。
#3 中的額外同步(面試官的建議)確實會帶來額外的開銷,更重要的是,它可能會在不需要時阻塞。“績效”是一個廣義的術語,對不同的人和在不同的環境中可能意味著不同的事物;但是根據情況,額外的同步可能(可能?)顯著影響觀察到的吞吐量。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/517039.html
標籤:爪哇多线程并发同步的
上一篇:多執行緒會幫助快速傅立葉變換嗎?
