日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

请在请求中携带deviceid参数_日常工作中最容易犯的几个并发错误,你中了几个?...

發布時間:2024/10/12 编程问答 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 请在请求中携带deviceid参数_日常工作中最容易犯的几个并发错误,你中了几个?... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:Jay_huaxiao

前言

列舉大家平時在工作中最容易犯的幾個并發錯誤,都是在實際項目代碼中看到的鮮活例子,希望對大家有幫助。

First Blood

線上總是出現:ERROR 1062 (23000) Duplicate entry 'xxx' for key 'yyy',我們來看一下有問題的這段代碼:

UserBindInfo info = selectFromDB(userId);if(info == null){info = new UserBindInfo(userId,deviceId);insertIntoDB(info);}else{info.setDeviceId(deviceId);updateDB(info);}

并發情況下,第一步判斷都為空,就會有2個或者多個線程進入插入數據庫操作, 這時候就出現了同一個ID插入多次。

正確處理姿勢:

insert into UserBindInfo values(#{userId},#{deviceId}) on duplicate key update deviceId=#{deviceId}多次的情況,導致插入失敗。

一般情況下,可以用insert...on duplicate key update... 解決這個問題。

注意: 如果UserBindInfo表存在主鍵以及一個以上的唯一索引,在并發情況下,使用insert...on duplicate key,可能會產生死鎖(Mysql5.7),可以這樣處理:

try{ UserBindInfoMapper.insertIntoDB(userBindInfo);}catch(DuplicateKeyException ex){ UserBindInfoMapper.update(userBindInfo);}

Double Kill

小心你的全局變量,如下面這段代碼:

public class GlobalVariableConcurrentTest { private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000)); while (true){ threadPoolExecutor.execute(()->{ String dateString = sdf.format(new Date()); try { Date parseDate = sdf.parse(dateString); String dateString2 = sdf.format(parseDate); System.out.println(dateString.equals(dateString2)); } catch (ParseException e) { e.printStackTrace(); } }); } }}

可以看到有異常拋出

全局變量的SimpleDateFormat,在并發情況下,存在安全性問題,阿里Java規約明確要求謹慎使用它。

除了SimpleDateFormat,其實很多時候,面對全局變量,我們都需要考慮并發情況是否存在問題,如下

@Componentpublic class Test { public static List desc = new ArrayList<>(); public List getDescByUserType(int userType) { if (userType == 1) { desc.add("普通會員不可以發送和查看郵件,請購買會員"); return desc; } else if (userType == 2) { desc.add("恭喜你已經是VIP會員,盡情的發郵件吧"); return desc; }else { desc.add("你的身份未知"); return desc; } }}

因為desc是全局變量,在并發情況下,請求getDescByUserType方法,得到的可能并不是你想要的結果。

Trible Kill

假設現在有如下業務:控制同一個用戶訪問某個接口的頻率不能小于5秒。一般很容易想到使用redis的 setnx操作來控制并發訪問,于是有以下代碼:

if(RedisOperation.setnx(userId, 1)){RedisOperation.expire(userId,5,TimeUnit.SECONDS));//執行正常業務邏輯}else{return “訪問過于頻繁”;}

假設執行完setnx操作,還沒來得及設置expireTime,機器重啟或者突然崩潰,將會發生死鎖。該用戶id,后面執行setnx永遠將為false,這可能讓你永遠損失那個用戶。

那么怎么解決這個問題呢,可以考慮用SET key value NX EX max-lock-time ,它是一種在 Redis 中實現鎖的方法,是原子性操作,不會像以上代碼分兩步執行,先set再expire,它是一步到位。

客戶端執行以上的命令:

  • 如果服務器返回 OK ,那么這個客戶端獲得鎖。
  • 如果服務器返回 NIL ,那么客戶端獲取鎖失敗,可以在稍后再重試。
  • 設置的過期時間到達之后,鎖將自動釋放

Quadra Kill

我們看一下有關ConcurrentHashMap的一段代碼,如下:

//全局變量Map map = new ConcurrentHashMap(); Integer value = count.get(k);if(value == null){ map.put(k,1);}else{ map.put(k,value+1);}

假設兩條線程都進入 value==null,這一步,得出的結果是不是會變小?OK,客官先稍作休息,閉目養神一會,我們驗證一下,請看一個demo:

public static void main(String[] args) { for (int i = 0; i < 1000; i++) { testConcurrentMap(); } } private static void testConcurrentMap() { final Map count = new ConcurrentHashMap<>(); ExecutorService executorService = Executors.newFixedThreadPool(2); final CountDownLatch endLatch = new CountDownLatch(2); Runnable task = ()-> { for (int i = 0; i < 5; i++) { Integer value = count.get("k"); if (null == value) { System.out.println(Thread.currentThread().getName()); count.put("k", 1); } else { count.put("k", value + 1); } } endLatch.countDown(); }; executorService.execute(task); executorService.execute(task); try { endLatch.await(); if (count.get("k") < 10) { System.out.println(count); } } catch (Exception e) { e.printStackTrace(); }

表面看,運行結果應該都是10對吧,好的,我們再看運行結果 :

運行結果出現了5,所以這樣實現是有并發問題的,那么正確的實現姿勢是啥呢?

Map map = new ConcurrentHashMap(); V v = map.get(k);if(v == null){ v = new V(); V old = map. putIfAbsent(k,v); if(old != null){ v = old; }}

可以考慮使用putIfAbsent解決這個問題

(1)如果key是新的記錄,那么會向map中添加該鍵值對,并返回null。

(2)如果key已經存在,那么不會覆蓋已有的值,返回已經存在的值

我們再來看看以下代碼以及運行結果:

public static void main(String[] args) { for (int i = 0; i < 1000; i++) { testConcurrentMap(); } } private static void testConcurrentMap() { ExecutorService executorService = Executors.newFixedThreadPool(2); final Map map = Maps.newConcurrentMap(); final CountDownLatch countDownLatch = new CountDownLatch(2); Runnable task = ()-> { AtomicInteger oldValue; for (int i = 0; i < 5; i++) { oldValue = map.get("k"); if (null == oldValue) { AtomicInteger initValue = new AtomicInteger(0); oldValue = map.putIfAbsent("k", initValue); if (oldValue == null) { oldValue = initValue; } } oldValue.incrementAndGet(); } countDownLatch.countDown(); }; executorService.execute(task); executorService.execute(task); try { countDownLatch.await(); System.out.println(map); } catch (Exception e) { e.printStackTrace(); } }

Penta Kill

現有如下業務場景:用戶手上有一張現金券,可以兌換相應的現金,

錯誤示范一

if(isAvailable(ticketId){1、給現金增加操作2、deleteTicketById(ticketId)}else{return “沒有可用現金券”}

解析: 假設有兩條線程A,B兌換現金,執行順序如下:

  • 1.線程A加現金
  • 2.線程B加現金
  • 3.線程A刪除票標志
  • 4.線程B刪除票標志

顯然,這樣有問題了,已經給用戶加了兩次現金了。

錯誤示范2

if(isAvailable(ticketId){1、deleteTicketById(ticketId)2、給現金增加操作}else{return “沒有可用現金券”}

并發情況下,如果一條線程,第一步deleteTicketById刪除失敗了,也會多添加現金。

正確處理方案

if(deleteAvailableTicketById(ticketId) == 1){1、給現金增加操作}else{return “沒有可用現金券”}

來源:掘金 鏈接:https://juejin.im/post/5dfec2806fb9a016043a59a6

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的请在请求中携带deviceid参数_日常工作中最容易犯的几个并发错误,你中了几个?...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。