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