摘要:場景在日常開發中經常遇到先根據條件判斷某條數據是否存在,如果不存在的話就插入,如果存在的話就更新或提示異常。查詢名字叫的用戶是否存在如果不存在就插入數據使用鎖其實和代碼塊是相同的作用,但是要注意必須在中釋放鎖,避免出現異常死鎖了。
場景:
在日常開發中經常遇到先根據條件判斷某條數據是否存在,如果不存在的話就插入,如果存在的話就更新或提示異常。一般代碼的模式都寫成下面這個樣子,是一種很常見的寫法,但是在并發情況下很容易會重復插入兩條數據,大概的情況就是第一個請求進來,沒有查詢到該用戶通過了if判斷,但是if中有比較耗時的邏輯,在第一個請求還沒執行insert的時候第二個請求也進來了,因為這個時候第一個請求還沒執行insert操作,所以第二個請求也沒有查詢到該用戶也通過了if判斷,這個樣子就造成了兩條重復的數據。
// 查詢名字叫user1的用戶是否存在 UserVo userVo= userMapper.selectUserByName("user1"); // 如果不存在就插入數據 if (userVo==null) { Thread.sleep(10000); UserVo userVo = new UserVo(); userVo.setUserName("user1"); userMapper.insert(userVo); } }解決方法: 1.使用synchronized同步代碼塊
直接將查詢校驗邏輯和插入邏輯都進行同步,也就是說第一個請求的邏輯沒結束,第二個請求就會一直等待著,只有當第一個請求執行完同步代碼塊中的邏輯釋放鎖后第二個請求才能獲取到鎖執行這段邏輯。
private Object obj = new Object(); synchronized (object){ // 查詢名字叫user1的用戶是否存在 UserVo userVo= userMapper.selectUserByName("user1"); // 如果不存在就插入數據 if (userVo==null) { Thread.sleep(10000); UserVo userVo = new UserVo(); userVo.setUserName("user1"); userMapper.insert(userVo); } }2.使用Lock鎖
其實和synchronized代碼塊是相同的作用,但是要注意必須在finally中釋放鎖,避免出現異常死鎖了。
private Lock lock = new ReentrantLock(); try { lock.lock(); // 查詢名字叫user1的用戶是否存在 UserVo userVo = userMapper.selectUserByName("user1"); // 如果不存在就插入數據 if (userVo == null) { Thread.sleep(10000); UserVo userVo = new UserVo(); userVo.setUserName("user1"); userMapper.insert(userVo); } } finally { lock.unlock(); }3.給數據庫索引
既然是要根據用戶名字判斷是否有重復數據,所以可以直接在數據庫上給userName字段添加UNIQUE索引,這樣在第二次重復插入的時候就會提示異常。如果不想重復插入的時候有報錯提示可以使用INSERT IGNORE INTO語句。而代碼則不必做任何邏輯操作。
// 查詢名字叫user1的用戶是否存在 UserVo userVo= userMapper.selectUserByName("user1"); // 如果不存在就插入數據 if (userVo==null) { Thread.sleep(10000); UserVo userVo = new UserVo(); userVo.setUserName("user1"); userMapper.insert(userVo); } }4.使用redis中setnx來作為鎖
redis中setnx命令是只有當你存入的key不存在時才會成功存入,并返回1,而如果key已經存在的時候則存入失敗并返回0,我們可以拿這個特性來當做鎖。首先這個方法進來第一步就是執行setnx操作,把查詢的用戶名存入redis,然后查詢該用戶是否存在,第一個請求進到if判斷中但是沒執行插入邏輯,第二個請求雖然也沒有查詢到該用戶,但是它的setnx會失敗,因為第一個請求存的key還沒刪除,所以這樣就避免了并發重新插入的問題,而且最大的優點就是它不像synchronized和Lock無論所有請求進來都只能一個一個通過,使用這種方法是只有當操作同一個用戶有并發請求的時候才會阻塞,而如果是請求兩個不同的用戶時是不會阻塞的,都可以順利通過,因為存入的key是不同的。
// 自動注入spring的redis操作類 @Autowired private RedisTemplate redisTemplate; public String addUser (String userName) { // 執行setnx命令,存入當前拿來判斷的用戶名 BoundValueOperations operations = redisTemplate.boundValueOps(userName); // 執行setnx命令的結果,這里封裝的方法是直接返回true和false boolean addFlag = operations.setIfAbsent(1); // 返回結果 String result = null; UserVo userVo= userMapper.selectUserByName(userName); try { if (userVo == null && addFlag == true) { Thread.sleep(10000); UserVo userVo = new UserVo(); userVo.setUserName("user1"); userMapper.insert(userVo); result = "更新成功"; } else{ result = "更新失敗"; } } finally { // 無論更新成功和失敗都去刪除setnx添加的key operations.getOperations().delete(userName); } return result; }總結:
上述四種方法,給數據庫加索引、Lock和redis都有使用過,synchronized和Lock也差不多,個人感覺給數據庫加索引來控制這種并發太死板了,萬一系統中有其他地方的邏輯是需要重復添加這個字段的數據,這個時候就沒辦法使用索引了,synchronized和Lock效率太低了,如果是并發量太大的這種方式肯定是不可缺的,而redis的這種方法則效率高很多,比較適合并發量高的操作。
結尾:因為本人接觸的系統的并發量也不是很大,所以對這方面的技術也是自己在鉆研摸索,可能會有很多地方有遺漏和錯誤,如果大家有更好的方法歡迎一起留言討論,也歡迎指出錯誤。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77799.html
摘要:每年支付寶在雙和雙的活動中,都展示了絕佳的技術能力。對于異步并發重復消息的處理亦是如此,加深對狀態機的判斷后還可以處理消息亂序問題。 每年支付寶在雙11和雙12的活動中,都展示了絕佳的技術能力。這個能力不但體現在處理高TPS量的訪問,更體現在幾乎不會出錯,不會出現重復支付的情況,那這個是怎么做到的呢? 誠然,為了實現在高并發下仍不會出錯的技術目標,支付寶下了很多功夫,比如冪等性的處理,...
摘要:每年支付寶在雙和雙的活動中,都展示了絕佳的技術能力。對于異步并發重復消息的處理亦是如此,加深對狀態機的判斷后還可以處理消息亂序問題。 每年支付寶在雙11和雙12的活動中,都展示了絕佳的技術能力。這個能力不但體現在處理高TPS量的訪問,更體現在幾乎不會出錯,不會出現重復支付的情況,那這個是怎么做到的呢? 誠然,為了實現在高并發下仍不會出錯的技術目標,支付寶下了很多功夫,比如冪等性的處理,...
文章內容通常是闡述了python查詢本身拼裝所有庫并導出,主要包括查詢拼裝庫依據命令查詢,導出庫安裝文件運行指令,原文中給大家介紹得相當詳細,對于大家學習與工作具有極強的參考文獻參照實際意義,務必的朋友可以參考一下 一、查詢拼裝庫 1.命令查詢 piplist 2.從安裝路徑site-packages查詢 二、導出庫安裝文件 1.導出 在我們要導出的庫文件夾內運行指令: pip...
閱讀 1710·2021-11-11 10:58
閱讀 4183·2021-09-09 09:33
閱讀 1256·2021-08-18 10:23
閱讀 1547·2019-08-30 15:52
閱讀 1623·2019-08-30 11:06
閱讀 1866·2019-08-29 14:03
閱讀 1506·2019-08-26 14:06
閱讀 2942·2019-08-26 10:39