解決高併發環境下Java多線程數據插入重複問題,有一個解決博客網站多線程博客網站數據重複的案例可供參考:
1.背景描述
應用框架:Spring + SpringMVC + Hibernate
數據庫:Oracle11g
一家博客網站向我係統推多線程低併發推送數據,我這邊觀察日誌和數據庫,發現有一個做者被存儲了2次到數據庫中。按照程序的編寫邏輯,重複的數據應當是會被判斷出來不被存儲的。
2.緣由分析
多是因爲網絡緣由,客戶可能連續推送了兩條重複的數據,兩條數據時間間隔很是小,所以致使了咱們的數據庫
if(用戶不存在) { xxxxx 存儲用戶到數據庫 } else { 重複推送,不採起任何措施 }
這個操做尚未執行完畢,第二條擁有相同數據的線程已經進入並經過了if的檢驗,致使數據庫存儲了兩條相同的數據。後來我本身寫了個100併發的多線程測試程序,發現100條相同數據中有40條被插入到了數據庫裏!所以肯定了是多線程的併發致使了程序的判斷邏輯失效。
3.解決思路:
1) 在Author主表中對 身份證號 添加了惟一索引,如今Author主表不會出現重複數據了,若是連續推送客戶會收到一條推送失敗的提示信息。
2) 可是AuthorOrg表(Author與AuthorOrg是一對多關係)依然會出現重複數據,想過添加siteId + userUniqueId的 聯合惟一索引來解決,可是想到work表也會出現一樣的問題,添加過多索引會致使DB佔用空間無限增大,所以不採用。
3) 考慮使用synchronized對方法添加同步鎖,可是這樣會致使其餘正常數據的推送線程也被阻塞,影響效率。所以不採用。
4) 使用對數據庫添加行鎖,實驗發現仍是會出現2條重複數據服務器
分析:網絡
理論上的結果應該是1條成功,149條失敗。
對數據庫的select語句添加行鎖必須做用於某條記錄,可是第一次報送時,數據庫中並無這條數據,所以行鎖根本沒有加上,致使第二條數據成功異步使用select語句。
第一次報送成功之後,數據庫中有了這條數據,select語句成功的對這條記錄添加了行鎖,因此後邊不會出現重複數據。所以此法不可用。
5) 即想提升效率不對方法添加synchronized,又想保證數據準確性,最後使用synchronized(siteId + uid) 在Controller層加鎖(保證了只有重複數據被加鎖,在Controller使用的緣由是由於事務會在Service調用完畢才被提交,我實驗過在Service同步,150併發會出現2條重複數據,由於事務還沒來得及提交)
測試結果:測試了3次150併發,不到一秒的時間所有返回,結果1條登記成功,149條返回該做者已登記。
下一步:
針對全部可能出現高併發問的接口進行調整。
4.提示
這種加同步鎖的方法在負載均衡下的多臺應用服務器會失效!由於就算Spring保證了對象是單例的,可是多臺服務器確定是多個對象!所以synchronized將無效。解決方法是在數據庫層對該對接公司的惟一記錄加select鎖,這樣就能保證數據的不重複性,可是會下降該公司推送數據的效率(至關於逐條推送),可是公司與公司之間仍是並行推送的。還有一個方法就是將業務邏輯寫入存儲過程,而後對存儲過程加鎖,這種方法太麻煩了,需求有變更就必須去修改存儲過程,可是效率要比前者高得多。多線程