基於redis方式實現分散式鎖
阿新 • • 發佈:2020-11-25
目錄
一、傳統單jvm實現方式:synchronized
Test001.java
package com.itmayiedu; public class Test001 { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); for (int i = 0; i < 50; i++) { Thread thread = new Thread(threadDemo); thread.start(); } } } //執行緒安全問題,在同一個jvm中,多個執行緒共享同一個全域性變數做寫的操作的時候,可能會受到其他執行緒的干擾。 class ThreadDemo implements Runnable { // synchronized 至適合於單個jvm private static int count; // 重寫Runnable執行緒執行方法run @Override public synchronized void run() { count(); } private synchronized void count() { try { Thread.sleep(15); } catch (Exception e) { e.printStackTrace(); } count++; System.out.println(Thread.currentThread().getName() + ",count:" + count); } }
執行結果(部分):
二、基於redis方式實現分散式鎖
LockRedis.java
package com.itmayiedu; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; public class LockRedis { // redis執行緒池 private JedisPool jedisPool; // 同時在redis上建立相同的一個key 相同key名稱 private String redislockKey = "redis_lock"; public LockRedis(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 分散式鎖最終核心思路:保證只能有一個jvm進行操作 * * 基於Redis實現分散式鎖原理:用setnx命令 * setnx命令可以做寫入key操作,可以獲取返回結果 * 該返回如果是為1,表示key不存在,寫入成功 * 該返回如果是位0,表示key已存在,寫入失敗 * * * 兩個超時時間含義: * 1.在獲取鎖之前的超時時間----在嘗試獲取鎖的時候,如果在規定的時間內還沒有獲取鎖,直接放棄。 * 2.在獲取鎖之後的超時時間---當獲取鎖成功之後,對應的key有對應有效期,對應的key在規定時間內進行失效 */ /** * @param acquireTimeout 在獲取鎖之前的超時時間 * @param timeOut 在獲取鎖之後的超時時間 * @return */ public String getRedisLock(Long acquireTimeout, Long timeOut) { Jedis conn = null; try { //1.建立redis連線 conn = jedisPool.getResource(); //2.定義redis對應key的value值(uuid),隨機生成不重複的value,相當於鎖的id String identifierValue = UUID.randomUUID().toString(); //3.定義在獲取鎖之後的超時時間(相當於value有效期) int expireLock = (int) (timeOut / 1000);// 以秒為單位 //4.定義在獲取鎖之前的超時時間 Long endTime = System.currentTimeMillis() + acquireTimeout; //5.使用迴圈機制,要在規定acquireTimeout時間保證重複進行嘗試獲取鎖(樂觀鎖) while (System.currentTimeMillis() < endTime) { //6.嘗試獲取鎖,使用setnx命令插入對應的redislockKey,如果返回為1,成功獲取鎖 if (conn.setnx(redislockKey, identifierValue) == 1) { conn.expire(redislockKey, expireLock); //設定鎖的key的有效期 return identifierValue; //返回鎖的id } } /* * 問:為什麼獲取鎖之後,還要設定鎖的超時時間? * 答:目的是為了防止死鎖 * * 問:zookeeper實現分散式鎖通過什麼方式防止死鎖? * 答:設定session有效期 * */ } catch (Exception e) { e.printStackTrace(); } finally { if (conn != null) { conn.close(); } } return null; } //PS:如果直接使用conn.del(redislockKey);保證對應是自己的建立redislockKey 刪除對應自己的。 /** * 釋放redis鎖 * 釋放鎖有兩種: * 1.key自動有有效期 * 2.整個程式執行完畢情況下,刪除對應key * * @param identifierValue 佔有鎖的執行緒的鎖的key對應的value值,相當於鎖的id */ public void unRedisLock(String identifierValue) { Jedis conn = null; // 1.建立redis連線 conn = jedisPool.getResource(); try { // 如果redis鎖的id等於identifierValue,表示是同一把鎖情況,才可以刪除 if (conn.get(redislockKey).equals(identifierValue)) { System.out.println("執行緒" + Thread.currentThread().getName() + "釋放鎖..." + ",鎖的id(identifierValue)為:" + identifierValue); conn.del(redislockKey); //刪除key } } catch (Exception e) { e.printStackTrace(); } finally { if (conn != null) { conn.close(); } } } }
LockService.java
package com.itmayiedu; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class LockService { private static JedisPool pool = null; static { JedisPoolConfig config = new JedisPoolConfig(); // 設定最大連線數 config.setMaxTotal(200); // 設定最大空閒數 config.setMaxIdle(8); // 設定最大等待時間 100s config.setMaxWaitMillis(1000 * 100); // 在borrow一個jedis例項時,是否需要驗證,若為true,則所有jedis例項均是可用的 config.setTestOnBorrow(true); // 自己redis伺服器的埠,自行設定。 // 分別表示:redis執行緒池配置物件、redis伺服器IP、redis服務埠、超時時間、redis服務密碼(沒有就不寫) pool = new JedisPool(config, "192.168.3.51", 6379, 2000, "123456"); } private LockRedis lockRedis = new LockRedis(pool); // 演示redis實現分散式鎖 public void seckill() { // 1.獲取鎖 String identifierValue = lockRedis.getRedisLock(2000l, 2000l); //獲取鎖前等待2s,獲取鎖後有2s有效期 if (identifierValue == null) { System.out.println("執行緒" + Thread.currentThread().getName() + ",獲取鎖失敗,因為獲取鎖時間超時..."); return; } System.out.println("執行緒" + Thread.currentThread().getName() + ",獲取鎖成功,鎖的id為:" + identifierValue + ",正常執行業務邏輯"); // 2.釋放鎖 lockRedis.unRedisLock(identifierValue); } }
ThreadRedis.java
package com.itmayiedu;
public class ThreadRedis extends Thread {
private LockService lockService;
public ThreadRedis(LockService lockService) {
this.lockService = lockService;
}
@Override
public void run() {
lockService.seckill();
}
}
Test002.java
package com.itmayiedu;
public class Test002 {
public static void main(String[] args) {
LockService lockService = new LockService();
for (int i = 0; i < 50; i++) {
new ThreadRedis(lockService).start();
}
}
}
執行結果(部分):