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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java实现分布式redis锁_使用redis实现分布式锁

發(fā)布時間:2023/12/15 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java实现分布式redis锁_使用redis实现分布式锁 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

# 簡介:

當(dāng)高并發(fā)訪問某個接口的時候,如果這個接口訪問的數(shù)據(jù)庫中的資源,并且你的數(shù)據(jù)庫事務(wù)級別是可重復(fù)讀(Repeatable read)的話,確實是沒有線程問題的,因為數(shù)據(jù)庫鎖的級別就夠了;但是如果這個接口需要訪問一個靜態(tài)變量、靜態(tài)代碼塊、全局緩存的中的資源或者redis中的資源的時候,就會出現(xiàn)線程安全的問題。

## 案例:

**github地址:** https://github.com/mzd123/mywy/tree/master/src/main/java/com/mzd/mywy/service

```

@RestController

public class MsController {

@Autowired

private MsService msService;

@RequestMapping("/select_info.do")

public String select_info(String product_id) {

return msService.select_info(product_id);

}

@RequestMapping("/order.do")

public String order(String product_id) throws CongestionException {

return msService.order1(product_id);

}

}

```

```

@Service

public class MsService {

@Autowired

private RedisLock redisLock;

//商品詳情

private static HashMap product = new HashMap();

//訂單表

private static HashMap orders = new HashMap();

//庫存表

private static HashMap stock = new HashMap();

static {

product.put("123", 10000);

stock.put("123", 10000);

}

public String select_info(String product_id) {

return "限量搶購商品XXX共" + product.get(product_id) + ",現(xiàn)在成功下單" + orders.size()

+ ",剩余庫存" + stock.get(product_id) + "件";

}

/**

* 下單

*

* @param product_id

* @return

*/

public String order1(String product_id) {

if (stock.get(product_id) == 0) {

return "活動已經(jīng)結(jié)束了";

//已近買完了

} else {

//還沒有賣完

try {

//模擬操作數(shù)據(jù)庫

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

orders.put(MyStringUtils.getuuid(), product_id);

stock.put(product_id, stock.get(product_id) - 1);

}

return select_info(product_id);

}

}

```

如上圖所述,我現(xiàn)在需要限購10000個商品id為123的商品,如果什么操作也不做,直接訪問靜態(tài)資源你們覺得會有問題嗎?我們使用apache_ab來模擬一下高并發(fā)情況,下面是發(fā)起100個請,并發(fā)量是50的情況:

![在這里插入圖片描述](https://img-blog.csdn.net/20180918091230687?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

**問題:** 可以看到,下單數(shù)和庫存加起來明顯超過了商品總數(shù),這是一種超賣現(xiàn)象,在java角度來說就是線程不安全現(xiàn)象。

**解決1:** 學(xué)過javase的小伙伴應(yīng)該都能想到使用synchronized關(guān)鍵字,強行同步。

```

/**

* 下單

*

* @param product_id

* @return

*/

public synchronized String order2(String product_id) {

if (stock.get(product_id) == 0) {

return "活動已經(jīng)結(jié)束了";

//已近買完了

} else {

//還沒有賣完

try {

//模擬操作數(shù)據(jù)庫

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

orders.put(MyStringUtils.getuuid(), product_id);

stock.put(product_id, stock.get(product_id) - 1);

}

return select_info(product_id);

}

```

![在這里插入圖片描述](https://img-blog.csdn.net/20180918094822673?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

**缺點:**

1、我們可以明顯的看到速度變慢了,從原來的0.535秒變到了10.956秒,那是因為synchronized放這個方法只允許單線程訪問了。

2、synchronized是粗粒度的控制了線程安全,即:如果我這個商品id不一樣的線程,理論上是可以同時訪問這個方法的,但是加上了synchronized之后,無論商品id是否一樣,兩個線程都是沒法同時訪問這個方法的。

**解決2:** 使用redis分布式鎖(主要使用了redis中的setnxgetset方法,這兩個方法在redisTemplate分別是setIfAbsentgetAndSet方法)實現(xiàn)線程安全,因為redis是單線程,能保證線程的安全性,而且redis強大的讀寫能力能提高效率。

```

/**

* 高并發(fā)沒問題,效率還行

*

* @param product_id

* @return

*/

public String order3(String product_id) throws CongestionException {

/**

* redis加鎖

*/

String value = System.currentTimeMillis() + 10000 + "";

if (!redisLock.lock1(product_id, value)) {

//系統(tǒng)繁忙,請稍后再試

throw new CongestionException();

}

//##############################業(yè)務(wù)邏輯#################################//

if (stock.get(product_id) == 0) {

return "活動已經(jīng)結(jié)束了";

//已近買完了

} else {

//還沒有賣完

try {

//模擬操作數(shù)據(jù)庫

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

orders.put(MyStringUtils.getuuid(), product_id);

stock.put(product_id, stock.get(product_id) - 1);

}

//##############################業(yè)務(wù)邏輯#################################//

/**

* redis解鎖

*/

redisLock.unlock(product_id, value);

return select_info(product_id);

}

```

```

/**

* 用redis實現(xiàn)分布式鎖

*/

@Component

public class RedisLock {

@Autowired

private StringRedisTemplate redisTemplate;

//加鎖

public boolean lock1(String key, String value) {

//setIfAbsent相當(dāng)于jedis中的setnx,如果能賦值就返回true,如果已經(jīng)有值了,就返回false

//即:在判斷這個key是不是第一次進(jìn)入這個方法

if (redisTemplate.opsForValue().setIfAbsent(key, value)) {

//第一次,即:這個key還沒有被賦值的時候

return true;

}

return false;

}

//解鎖

public void unlock(String key, String value) {

try {

if (MyStringUtils.Object2String(redisTemplate.opsForValue().get(key)).equals(value)) {

redisTemplate.opsForValue().getOperations().delete(key);

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

```

**缺點:** 這個方法看上去沒什么問題,而且只有商品id相同的兩個線程同時訪問這個方法的時候才會出現(xiàn)線程問題,這似乎是很完美了。但是有沒有想過,萬一處理業(yè)務(wù)邏輯的代碼塊中出現(xiàn)了異常,直接拋了出去,那解鎖的代碼就再也不會被執(zhí)行了,也就是出現(xiàn)了死鎖現(xiàn)象。

**改進(jìn)1:**

```

public boolean lock2(String key, String value) {

//setIfAbsent相當(dāng)于jedis中的setnx,如果能賦值就返回true,如果已經(jīng)有值了,就返回false

//即:在判斷這個key是不是第一次進(jìn)入這個方法

if (redisTemplate.opsForValue().setIfAbsent(key, value)) {

//第一次,即:這個key還沒有被賦值的時候

return true;

}

String current_value = redisTemplate.opsForValue().get(key);//①

if (!MyStringUtils.Object2String(current_value).equals("")

//超時了

&& Long.parseLong(current_value) < System.currentTimeMillis()) {;//②

//返回true就能解決死鎖

return true;

}

return false;

}

```

**缺點:** 使用超時時間來解決死鎖問題,但是又出現(xiàn)新的問題,就是當(dāng)有兩個商品id相同的線程同時執(zhí)行到了②這一行代碼,這時候兩個線程同時獲取鎖,這樣一來任然存在線程安全問題了。。。

**改進(jìn)2:**

```

/**

* 加鎖

*/

public boolean lock3(String key, String value) {

//setIfAbsent相當(dāng)于jedis中的setnx,如果能賦值就返回true,如果已經(jīng)有值了,就返回false

//即:在判斷這個key是不是第一次進(jìn)入這個方法

if (redisTemplate.opsForValue().setIfAbsent(key, value)) {

//第一次,即:這個key還沒有被賦值的時候

return true;

}

String current_value = redisTemplate.opsForValue().get(key);

if (!MyStringUtils.Object2String(current_value).equals("")

//超時了

&& Long.parseLong(current_value) < System.currentTimeMillis()) {//①

String old_value = redisTemplate.opsForValue().getAndSet(key, value);//②

if (!MyStringUtils.Object2String(old_value).equals("")

&& old_value.equals(current_value)) {

return true;

}

}

return false;

}

```

**解釋:** 如果兩個線程同時調(diào)用這個方法,當(dāng)同時走到①的時候,無論怎么樣都有一個線程會先執(zhí)行②這一行,假設(shè)線程1先執(zhí)行②這行代碼,那redis中key對應(yīng)的value就變成了value,然后線程2再執(zhí)行②這行代碼的時候,獲取到的old_value就是value,那么value顯然和他上面獲取的current_value是不一樣的,則線程2是沒法獲取鎖的。

![在這里插入圖片描述](https://img-blog.csdn.net/20180918105344515?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

![在這里插入圖片描述](https://img-blog.csdn.net/20180918105442595?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

**說明:** 雖然100個請求只有2個成功下單的,但是耗時卻明顯變小了,而且線程也是安全的,只是絕大部分因為沒有拿到鎖而沒有搶到限購的商品,但也做了人性化的提醒,個人覺得還是可以接受的!

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

總結(jié)

以上是生活随笔為你收集整理的java实现分布式redis锁_使用redis实现分布式锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。