关于J2EE中死锁问题的研究(2)
生活随笔
收集整理的這篇文章主要介紹了
关于J2EE中死锁问题的研究(2)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
跨資源死鎖情形之1:客戶端的增加導(dǎo)致資源池耗盡
我們要介紹的第一種死鎖情形是單純由于負載而造成的,即資源池太小,而每個線程需要的資源超過了池中的可用資源。例如,考慮一個使用數(shù)據(jù)庫連接的EJB調(diào)用,執(zhí)行一個嵌套的EJB調(diào)用(使用同一連接池中不同的數(shù)據(jù)庫連接)。例如,如果該嵌套的EJB調(diào)用聲明為RequiresNew,就會出現(xiàn)死鎖情形。
在正常負載或者有足夠大小的連接池的情況下,EJB調(diào)用將從池中獲取一個數(shù)據(jù)庫連接,然后調(diào)用嵌套的EJB。嵌套的EJB調(diào)用將從池中獲取另一個數(shù)據(jù)庫連接,提交內(nèi)部事務(wù),然后向池返回連接。外部EJB調(diào)用將提交自己的事務(wù),并向池返回其連接。
但是,假設(shè)連接池最多有10個連接,同時有10個對外部EJB的并發(fā)調(diào)用。這些線程中每一個都需要一個數(shù)據(jù)庫連接用來清空池。現(xiàn)在,每個線程都執(zhí)行嵌套的EJB調(diào)用(需要獲取第二個數(shù)據(jù)庫連接)。則所有線程都不能繼續(xù),但又都不放棄自己的第一個數(shù)據(jù)庫連接。這樣,10個線程都將被死鎖。
如果研究此類死鎖情形,會發(fā)現(xiàn)線程轉(zhuǎn)儲中有大量等待獲取資源的線程,以及同等數(shù)量的空閑且未阻塞的活動數(shù)據(jù)庫連接。當(dāng)應(yīng)用程序死鎖時,如果可以在運行時檢測連接池,應(yīng)該能確認連接池實際上已空。
修復(fù)此類死鎖的方法包括:增加連接池的大小或者重構(gòu)代碼,以便單個線程不需要同時使用很多數(shù)據(jù)庫連接。如果單線程需要的最大數(shù)據(jù)庫連接數(shù)為M,且可能的最大并發(fā)調(diào)用數(shù)為N,則要避免此問題,在池中所需的最小連接數(shù)為(N*(M01))+1。或者可以設(shè)置內(nèi)部EJB調(diào)用以使用不同的連接池,即使外部調(diào)用的連接池為空,內(nèi)部調(diào)用也能使用自己的連接池繼續(xù)。
跨資源死鎖情形之2:單線程、多沖突數(shù)據(jù)庫連接
對同一線程執(zhí)行嵌套的EJB調(diào)用時還會出現(xiàn)第二種跨資源死鎖情形,此情形即使在非高負載系統(tǒng)中通常也會發(fā)生。同上面的示例一樣,兩個EJB調(diào)用使用不同的連接來連接到同一個數(shù)據(jù)庫。因為只有嵌套調(diào)用完成后調(diào)用方才能繼續(xù),所以調(diào)用方的數(shù)據(jù)庫連接實際上被嵌套調(diào)用的數(shù)據(jù)庫連接阻塞了,雖然數(shù)據(jù)庫沒有注意到這種關(guān)系。如果第一個(外部)連接已獲取第二個(內(nèi)部)連接所需要的數(shù)據(jù)庫鎖,則第二個連接將永久阻塞第一個連接,并等待第一個連接被提交或回滾,這就出現(xiàn)了死鎖情形。因為數(shù)據(jù)庫沒有注意到兩個連接之間的關(guān)系,所以數(shù)據(jù)庫不會將此情形檢測為死鎖。
作為一個具體的示例,考慮一個數(shù)據(jù)加載EJB調(diào)用。此EJB調(diào)用獲取一個大型對象,并在不同階段中將其保存在數(shù)據(jù)庫中。當(dāng)它執(zhí)行數(shù)據(jù)加載時,它會更新一個單獨的表,以記錄掛起數(shù)據(jù)加載操作的狀態(tài)。我們希望狀態(tài)更新立即可見,但不希望在未完成的狀態(tài)下看到加載的數(shù)據(jù),所以要通過調(diào)用“RequiresNew” EJB來完成。總的來說,這種不完善的數(shù)據(jù)加載方法如清單1中的代碼所示。
清單1 public void bulkLoadData(DataBatch batch) { int batchId = batch.getId(); // Since this executeUpdate call doesn誸 happen in a separate // transaction, it wouldn't be visible anyway, but the effect is // far worse: a cross-resource deadlock. executeUpdate("update batch_status set status='Started' " + "where batch_id=" + batchId); validateData(batch); updateBatchStatus(batchId, "Validated"); // RequiresNew EJB call loadDataStage1(batch); updateBatchStatus(batchId, "Stage 1 complete"); // RequiresNew EJB call loadDataStage2(batch); updateBatchStatus(batchId, "Stage 2 complete"); // RequiresNew EJB call finalizeDataLoad(batch); updateBatchStatus(batchId, "Complete"); // RequiresNew EJB call } 在上面的示例中,使用updateBatchStatus方法執(zhí)行“RequiresNew” EJB調(diào)用實際上可以更新batch_status數(shù)據(jù)庫表,即使沒有看到當(dāng)前事務(wù)的效果,也能立即看到狀態(tài)的改變。對executeUpdate的調(diào)用不是EJB調(diào)用,所以它和bulkLoadData的其他部分在同一個事務(wù)中執(zhí)行。
如上所述,即使不存在并發(fā),此代碼也將導(dǎo)致死鎖。當(dāng)bulkLoadData調(diào)用executeUpdate方法時,它更新現(xiàn)有的數(shù)據(jù)庫行,這涉及為該行獲取寫鎖。對updateBatchStatus的嵌套EJB調(diào)用將在單獨的數(shù)據(jù)庫連接上執(zhí)行,并嘗試執(zhí)行一個非常相似的查詢,但它將阻塞,因為不能獲取必需的寫鎖。從數(shù)據(jù)庫的角度來說,只要提交或回滾第一個連接的事務(wù),第二個連接就可以繼續(xù)。但是,Java虛擬機不允許在完成所有對updateBatchStatus的調(diào)用前完成bulkLoadD調(diào)用,這樣就出現(xiàn)了死鎖情形。
該示例表明,一個更新會阻塞另一個更新,所以它會在任何數(shù)據(jù)庫中導(dǎo)致死鎖。如果初始更新查詢是一個簡單的選擇查詢,那么該示例僅在使用基于鎖的并發(fā)控制的數(shù)據(jù)庫上導(dǎo)致死鎖,在這種數(shù)據(jù)庫中,一個連接的讀鎖可以阻止另一個連接獲取寫鎖。不管在哪種情況下,此類死鎖即不依賴于同步,也不依賴于負載,而且線程轉(zhuǎn)儲將顯示一個等待數(shù)據(jù)庫響應(yīng)的Java線程,但該線程與兩個有效的數(shù)據(jù)庫連接相關(guān)聯(lián)。在這些數(shù)據(jù)庫連接中,有一個將處于空閑狀態(tài),但會阻塞其他連接。
此情形有多種具體的變種,可以涉及多個線程和兩個以上的數(shù)據(jù)庫連接。例如,外部EJB調(diào)用的數(shù)據(jù)庫連接可能已經(jīng)獲取了數(shù)據(jù)庫鎖,該鎖阻塞了另一個無關(guān)數(shù)據(jù)庫連接的繼續(xù),但這個無關(guān)數(shù)據(jù)庫連接已經(jīng)獲取了阻塞嵌套EJB調(diào)用的數(shù)據(jù)庫操作的鎖。這個特例是依賴于同步的,并將顯示多個等待數(shù)據(jù)庫響應(yīng)的Java線程。其中至少有一個Java線程將與兩個活動數(shù)據(jù)庫連接相關(guān)聯(lián)。
跨資源死鎖情形之3:Java虛擬機鎖與數(shù)據(jù)庫鎖相沖突
第三種死鎖情形發(fā)生在數(shù)據(jù)庫鎖與Java虛擬機鎖并存的時候。在這種情況下,一個線程占有一個數(shù)據(jù)庫鎖并嘗試獲取Java虛擬機鎖(嘗試進入同步的鎖)。同時,另一個線程占有Java虛擬機鎖并嘗試獲取數(shù)據(jù)庫鎖。再次地,數(shù)據(jù)庫發(fā)現(xiàn)一個連接阻塞了另一個連接,但由于無法阻止連接繼續(xù),所以不會檢測到死鎖。Java虛擬機發(fā)現(xiàn)同步的鎖中有一個線程,并有另一個嘗試進入的線程,所以即使Java虛擬機能檢測到死鎖并對它們進行處理,它還是不會檢測到這種情況。
為了說明此種死鎖情形,我們以一個簡單的(不完善的)read-through cache為例。該cache是數(shù)據(jù)庫表中備份的HashMap。如果出現(xiàn)緩存命中,它就從HashMap返回一個值。但在緩存缺失的情況下,它將從數(shù)據(jù)庫讀取值,將其添加到HashMap,然后返回該值,如清單2所示。
清單 2 public class SimpleCache { private Map cache = new HashMap(); public synchronized Object get(String key) { if (cache.containsKey(key)) { return cache.get(key); } else { Object value = queryForValue(key); cache.put(key, value); return value; } } private Object queryForValue(String key) { return executeQuery("select value from cache_table " + "where key='" + key + "'"); } public synchronized void clearCache() { cache.clear(); } // other methods omitted for brevity }
這是一個簡單的遍歷cache。注意:get()方法是同步的,這是因為我們訪問了非線程安全容器,并要求containsKey/put組合在緩存缺失時是原子性的。
該cache相當(dāng)簡單易懂:它約定,如果更改支持緩存的表中的數(shù)據(jù),則應(yīng)調(diào)用clearCache(),這樣緩存就可以避免處理陳舊的數(shù)據(jù)。產(chǎn)生的緩存缺失將相應(yīng)地重新進入緩存。
我們現(xiàn)在來考慮可以更改此數(shù)據(jù)并清除緩存的代碼:
public void updateData(String key, String value) { executeUpdate("update cache_table set value='" + value + "' where key='" + key + "'"); SimpleCache.getInstance().clearCache(); } 上面的代碼在簡單的例子中能正常運行。但是,在使用基于鎖的并發(fā)控制的數(shù)據(jù)庫中,updateData中的查詢將阻止queryForValue中的選擇查詢的執(zhí)行,因為update語句將獲取一個寫鎖,從而阻止選擇查詢獲取同一數(shù)據(jù)行上的讀鎖。如果同步?jīng)]有問題,一個線程可以嘗試讀取緩存中的給定值,并在另一個線程在數(shù)據(jù)庫中更新該值時得到緩存缺失。如果數(shù)據(jù)庫先執(zhí)行update語句,它將阻塞select語句繼續(xù)執(zhí)行。但是,執(zhí)行select語句的線程來自同步的get方法,所以它獲取了SimpleCache上的鎖。要返回updateData中的線程,它必須調(diào)用clearCache(),但不能獲取鎖(clearCache()是同步的)。
當(dāng)處理此情形的實例時,將有一個等待數(shù)據(jù)庫響應(yīng)的Java線程和一個等待獲取Java虛擬機鎖的線程。每個線程將與一個數(shù)據(jù)庫連接相關(guān)聯(lián),其中一個連接阻塞另一個連接。修復(fù)方法是占有Java虛擬機鎖時避免執(zhí)行數(shù)據(jù)庫操作,可以重寫leCache的get()方法,如下所示:
?
public Object get(String key) { synchronized(this) { if (cache.containsKey(key)) { return cache.get(key); } } Object value = queryForValue(key); synchronized(this) { cache.put(key, value); } return value; }
既然現(xiàn)在我們知道了會發(fā)生此死鎖情況,就可以使用Thread.holdsLock()向queryForValue方法添加檢查以嘗試避免死鎖情況:
private Object queryForValue(String key) { assert(!Thread.holdsLock(this)); return executeQuery(...); } 上例中的Thread.holdsLock()很有用,但是只有在我們知道需要留心哪個鎖時它才會發(fā)揮作用。如果有一個類似的方法可以確定當(dāng)前線程占有哪個Java虛擬機鎖,那么會很有用。任何執(zhí)行任何種類的RPC調(diào)用、數(shù)據(jù)庫訪問等的代碼片段都可以拋出異常或記錄警告,指示在占有Java虛擬機鎖時執(zhí)行這些操作會有危險。
注意:雖然我們修復(fù)了上例中的死鎖問題,但它仍有缺陷,因為在提交updateData的事務(wù)之前清空了緩存。如果在調(diào)用clearCache后、提交updateData事務(wù)前出現(xiàn)緩存缺失,則該緩存將加載舊數(shù)據(jù),因為新數(shù)據(jù)尚未可見。這里的修復(fù)方法是僅在提交更改后清空緩存。注意,這只在MVCC數(shù)據(jù)庫中發(fā)生。在基于鎖的數(shù)據(jù)庫中,掛起的update將阻塞緩存的讀操作,所以在提交update的事務(wù)后緩存才能讀取正確值。
我們要介紹的第一種死鎖情形是單純由于負載而造成的,即資源池太小,而每個線程需要的資源超過了池中的可用資源。例如,考慮一個使用數(shù)據(jù)庫連接的EJB調(diào)用,執(zhí)行一個嵌套的EJB調(diào)用(使用同一連接池中不同的數(shù)據(jù)庫連接)。例如,如果該嵌套的EJB調(diào)用聲明為RequiresNew,就會出現(xiàn)死鎖情形。
在正常負載或者有足夠大小的連接池的情況下,EJB調(diào)用將從池中獲取一個數(shù)據(jù)庫連接,然后調(diào)用嵌套的EJB。嵌套的EJB調(diào)用將從池中獲取另一個數(shù)據(jù)庫連接,提交內(nèi)部事務(wù),然后向池返回連接。外部EJB調(diào)用將提交自己的事務(wù),并向池返回其連接。
但是,假設(shè)連接池最多有10個連接,同時有10個對外部EJB的并發(fā)調(diào)用。這些線程中每一個都需要一個數(shù)據(jù)庫連接用來清空池。現(xiàn)在,每個線程都執(zhí)行嵌套的EJB調(diào)用(需要獲取第二個數(shù)據(jù)庫連接)。則所有線程都不能繼續(xù),但又都不放棄自己的第一個數(shù)據(jù)庫連接。這樣,10個線程都將被死鎖。
如果研究此類死鎖情形,會發(fā)現(xiàn)線程轉(zhuǎn)儲中有大量等待獲取資源的線程,以及同等數(shù)量的空閑且未阻塞的活動數(shù)據(jù)庫連接。當(dāng)應(yīng)用程序死鎖時,如果可以在運行時檢測連接池,應(yīng)該能確認連接池實際上已空。
修復(fù)此類死鎖的方法包括:增加連接池的大小或者重構(gòu)代碼,以便單個線程不需要同時使用很多數(shù)據(jù)庫連接。如果單線程需要的最大數(shù)據(jù)庫連接數(shù)為M,且可能的最大并發(fā)調(diào)用數(shù)為N,則要避免此問題,在池中所需的最小連接數(shù)為(N*(M01))+1。或者可以設(shè)置內(nèi)部EJB調(diào)用以使用不同的連接池,即使外部調(diào)用的連接池為空,內(nèi)部調(diào)用也能使用自己的連接池繼續(xù)。
跨資源死鎖情形之2:單線程、多沖突數(shù)據(jù)庫連接
對同一線程執(zhí)行嵌套的EJB調(diào)用時還會出現(xiàn)第二種跨資源死鎖情形,此情形即使在非高負載系統(tǒng)中通常也會發(fā)生。同上面的示例一樣,兩個EJB調(diào)用使用不同的連接來連接到同一個數(shù)據(jù)庫。因為只有嵌套調(diào)用完成后調(diào)用方才能繼續(xù),所以調(diào)用方的數(shù)據(jù)庫連接實際上被嵌套調(diào)用的數(shù)據(jù)庫連接阻塞了,雖然數(shù)據(jù)庫沒有注意到這種關(guān)系。如果第一個(外部)連接已獲取第二個(內(nèi)部)連接所需要的數(shù)據(jù)庫鎖,則第二個連接將永久阻塞第一個連接,并等待第一個連接被提交或回滾,這就出現(xiàn)了死鎖情形。因為數(shù)據(jù)庫沒有注意到兩個連接之間的關(guān)系,所以數(shù)據(jù)庫不會將此情形檢測為死鎖。
作為一個具體的示例,考慮一個數(shù)據(jù)加載EJB調(diào)用。此EJB調(diào)用獲取一個大型對象,并在不同階段中將其保存在數(shù)據(jù)庫中。當(dāng)它執(zhí)行數(shù)據(jù)加載時,它會更新一個單獨的表,以記錄掛起數(shù)據(jù)加載操作的狀態(tài)。我們希望狀態(tài)更新立即可見,但不希望在未完成的狀態(tài)下看到加載的數(shù)據(jù),所以要通過調(diào)用“RequiresNew” EJB來完成。總的來說,這種不完善的數(shù)據(jù)加載方法如清單1中的代碼所示。
清單1 public void bulkLoadData(DataBatch batch) { int batchId = batch.getId(); // Since this executeUpdate call doesn誸 happen in a separate // transaction, it wouldn't be visible anyway, but the effect is // far worse: a cross-resource deadlock. executeUpdate("update batch_status set status='Started' " + "where batch_id=" + batchId); validateData(batch); updateBatchStatus(batchId, "Validated"); // RequiresNew EJB call loadDataStage1(batch); updateBatchStatus(batchId, "Stage 1 complete"); // RequiresNew EJB call loadDataStage2(batch); updateBatchStatus(batchId, "Stage 2 complete"); // RequiresNew EJB call finalizeDataLoad(batch); updateBatchStatus(batchId, "Complete"); // RequiresNew EJB call } 在上面的示例中,使用updateBatchStatus方法執(zhí)行“RequiresNew” EJB調(diào)用實際上可以更新batch_status數(shù)據(jù)庫表,即使沒有看到當(dāng)前事務(wù)的效果,也能立即看到狀態(tài)的改變。對executeUpdate的調(diào)用不是EJB調(diào)用,所以它和bulkLoadData的其他部分在同一個事務(wù)中執(zhí)行。
如上所述,即使不存在并發(fā),此代碼也將導(dǎo)致死鎖。當(dāng)bulkLoadData調(diào)用executeUpdate方法時,它更新現(xiàn)有的數(shù)據(jù)庫行,這涉及為該行獲取寫鎖。對updateBatchStatus的嵌套EJB調(diào)用將在單獨的數(shù)據(jù)庫連接上執(zhí)行,并嘗試執(zhí)行一個非常相似的查詢,但它將阻塞,因為不能獲取必需的寫鎖。從數(shù)據(jù)庫的角度來說,只要提交或回滾第一個連接的事務(wù),第二個連接就可以繼續(xù)。但是,Java虛擬機不允許在完成所有對updateBatchStatus的調(diào)用前完成bulkLoadD調(diào)用,這樣就出現(xiàn)了死鎖情形。
該示例表明,一個更新會阻塞另一個更新,所以它會在任何數(shù)據(jù)庫中導(dǎo)致死鎖。如果初始更新查詢是一個簡單的選擇查詢,那么該示例僅在使用基于鎖的并發(fā)控制的數(shù)據(jù)庫上導(dǎo)致死鎖,在這種數(shù)據(jù)庫中,一個連接的讀鎖可以阻止另一個連接獲取寫鎖。不管在哪種情況下,此類死鎖即不依賴于同步,也不依賴于負載,而且線程轉(zhuǎn)儲將顯示一個等待數(shù)據(jù)庫響應(yīng)的Java線程,但該線程與兩個有效的數(shù)據(jù)庫連接相關(guān)聯(lián)。在這些數(shù)據(jù)庫連接中,有一個將處于空閑狀態(tài),但會阻塞其他連接。
此情形有多種具體的變種,可以涉及多個線程和兩個以上的數(shù)據(jù)庫連接。例如,外部EJB調(diào)用的數(shù)據(jù)庫連接可能已經(jīng)獲取了數(shù)據(jù)庫鎖,該鎖阻塞了另一個無關(guān)數(shù)據(jù)庫連接的繼續(xù),但這個無關(guān)數(shù)據(jù)庫連接已經(jīng)獲取了阻塞嵌套EJB調(diào)用的數(shù)據(jù)庫操作的鎖。這個特例是依賴于同步的,并將顯示多個等待數(shù)據(jù)庫響應(yīng)的Java線程。其中至少有一個Java線程將與兩個活動數(shù)據(jù)庫連接相關(guān)聯(lián)。
跨資源死鎖情形之3:Java虛擬機鎖與數(shù)據(jù)庫鎖相沖突
第三種死鎖情形發(fā)生在數(shù)據(jù)庫鎖與Java虛擬機鎖并存的時候。在這種情況下,一個線程占有一個數(shù)據(jù)庫鎖并嘗試獲取Java虛擬機鎖(嘗試進入同步的鎖)。同時,另一個線程占有Java虛擬機鎖并嘗試獲取數(shù)據(jù)庫鎖。再次地,數(shù)據(jù)庫發(fā)現(xiàn)一個連接阻塞了另一個連接,但由于無法阻止連接繼續(xù),所以不會檢測到死鎖。Java虛擬機發(fā)現(xiàn)同步的鎖中有一個線程,并有另一個嘗試進入的線程,所以即使Java虛擬機能檢測到死鎖并對它們進行處理,它還是不會檢測到這種情況。
為了說明此種死鎖情形,我們以一個簡單的(不完善的)read-through cache為例。該cache是數(shù)據(jù)庫表中備份的HashMap。如果出現(xiàn)緩存命中,它就從HashMap返回一個值。但在緩存缺失的情況下,它將從數(shù)據(jù)庫讀取值,將其添加到HashMap,然后返回該值,如清單2所示。
清單 2 public class SimpleCache { private Map cache = new HashMap(); public synchronized Object get(String key) { if (cache.containsKey(key)) { return cache.get(key); } else { Object value = queryForValue(key); cache.put(key, value); return value; } } private Object queryForValue(String key) { return executeQuery("select value from cache_table " + "where key='" + key + "'"); } public synchronized void clearCache() { cache.clear(); } // other methods omitted for brevity }
這是一個簡單的遍歷cache。注意:get()方法是同步的,這是因為我們訪問了非線程安全容器,并要求containsKey/put組合在緩存缺失時是原子性的。
該cache相當(dāng)簡單易懂:它約定,如果更改支持緩存的表中的數(shù)據(jù),則應(yīng)調(diào)用clearCache(),這樣緩存就可以避免處理陳舊的數(shù)據(jù)。產(chǎn)生的緩存缺失將相應(yīng)地重新進入緩存。
我們現(xiàn)在來考慮可以更改此數(shù)據(jù)并清除緩存的代碼:
public void updateData(String key, String value) { executeUpdate("update cache_table set value='" + value + "' where key='" + key + "'"); SimpleCache.getInstance().clearCache(); } 上面的代碼在簡單的例子中能正常運行。但是,在使用基于鎖的并發(fā)控制的數(shù)據(jù)庫中,updateData中的查詢將阻止queryForValue中的選擇查詢的執(zhí)行,因為update語句將獲取一個寫鎖,從而阻止選擇查詢獲取同一數(shù)據(jù)行上的讀鎖。如果同步?jīng)]有問題,一個線程可以嘗試讀取緩存中的給定值,并在另一個線程在數(shù)據(jù)庫中更新該值時得到緩存缺失。如果數(shù)據(jù)庫先執(zhí)行update語句,它將阻塞select語句繼續(xù)執(zhí)行。但是,執(zhí)行select語句的線程來自同步的get方法,所以它獲取了SimpleCache上的鎖。要返回updateData中的線程,它必須調(diào)用clearCache(),但不能獲取鎖(clearCache()是同步的)。
當(dāng)處理此情形的實例時,將有一個等待數(shù)據(jù)庫響應(yīng)的Java線程和一個等待獲取Java虛擬機鎖的線程。每個線程將與一個數(shù)據(jù)庫連接相關(guān)聯(lián),其中一個連接阻塞另一個連接。修復(fù)方法是占有Java虛擬機鎖時避免執(zhí)行數(shù)據(jù)庫操作,可以重寫leCache的get()方法,如下所示:
?
public Object get(String key) { synchronized(this) { if (cache.containsKey(key)) { return cache.get(key); } } Object value = queryForValue(key); synchronized(this) { cache.put(key, value); } return value; }
既然現(xiàn)在我們知道了會發(fā)生此死鎖情況,就可以使用Thread.holdsLock()向queryForValue方法添加檢查以嘗試避免死鎖情況:
private Object queryForValue(String key) { assert(!Thread.holdsLock(this)); return executeQuery(...); } 上例中的Thread.holdsLock()很有用,但是只有在我們知道需要留心哪個鎖時它才會發(fā)揮作用。如果有一個類似的方法可以確定當(dāng)前線程占有哪個Java虛擬機鎖,那么會很有用。任何執(zhí)行任何種類的RPC調(diào)用、數(shù)據(jù)庫訪問等的代碼片段都可以拋出異常或記錄警告,指示在占有Java虛擬機鎖時執(zhí)行這些操作會有危險。
注意:雖然我們修復(fù)了上例中的死鎖問題,但它仍有缺陷,因為在提交updateData的事務(wù)之前清空了緩存。如果在調(diào)用clearCache后、提交updateData事務(wù)前出現(xiàn)緩存缺失,則該緩存將加載舊數(shù)據(jù),因為新數(shù)據(jù)尚未可見。這里的修復(fù)方法是僅在提交更改后清空緩存。注意,這只在MVCC數(shù)據(jù)庫中發(fā)生。在基于鎖的數(shù)據(jù)庫中,掛起的update將阻塞緩存的讀操作,所以在提交update的事務(wù)后緩存才能讀取正確值。
轉(zhuǎn)載于:https://www.cnblogs.com/xiefang1980/archive/2008/04/28/1174136.html
總結(jié)
以上是生活随笔為你收集整理的关于J2EE中死锁问题的研究(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PDF阅读器Foxit Reader 2
- 下一篇: 在php中使用mb_substr($ro