分布式鎖,是控制分布式系統之間同步訪問共享資源的一種方式。
在分布式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。
setnx 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。 命令格式:SETNX key value;使用:只在鍵 key 不存在的情況下,將鍵 key 的值設置為 value 。若鍵 key 已經存在, 則 SETNX 命令不做任何動作。返回值:命令在設置成功時返回 1 ,設置失敗時返回 0 。
getset 命令格式:GETSET key value,將鍵 key 的值設為 value ,並返回鍵 key 在被設置之前的舊的value。返回值:如果鍵 key 沒有舊值, 也即是說, 鍵 key 在被設置之前並不存在, 那么命令返回 nil 。當鍵 key 存在但不是字符串類型時,命令返回一個錯誤。
expire 命令格式:EXPIRE key seconds,使用:為給定 key 設置生存時間,當 key 過期時(生存時間為 0 ),它會被自動刪除。返回值:設置成功返回 1 。 當 key 不存在或者不能為 key 設置生存時間時(比如在低於 2.1.3 版本的 Redis 中你嘗試更新 key 的生存時間),返回 0 。
del 命令格式:DEL key [key …],使用:刪除給定的一個或多個 key ,不存在的 key 會被忽略。返回值:被刪除 key 的數量。
Redis實現分布式鎖的原理:
1.通過setnx(lock_timeout)實現,如果設置了鎖返回1, 已經有值沒有設置成功返回0
2.死鎖問題:通過實踐來判斷是否過期,如果已經過期,獲取到過期時間get(lockKey),然后getset(lock_timeout)判斷是否和get相同,相同則證明已經加鎖成功,因為可能導致多線程同時執行getset(lock_timeout)方法,這可能導致多線程都只需getset后,對於判斷加鎖成功的線程, 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)過期時間,防止多個線程同時疊加時間,導致鎖時效時間翻倍

代碼:
/**
* @author yaoxin
* @date 2018/8/13下午5:04
*/
public class RedisLockTest {
public static final String url = "jdbc:mysql://127.0.0.1:3306/ly?characterEncoding=UTF-8";
public static final String name = "com.mysql.jdbc.Driver";
public static final String user = "root";
public static final String password = "";
public static void main(String[] args) {
Integer count = 50;
while (count > 0) {
count--;
new Thread(new Runnable() {
@Override
public void run() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("1234");
String lock = lock(jedis);
if (lock != null) {
Statement statement = null;
Connection conn = null;
ResultSet resultSet = null;
try {
Class.forName(name);// 指定連接類型
conn = DriverManager.getConnection(url, user, password);// 獲取連接
statement = conn.createStatement();// 准備執行語句
String querySql = "SELECT id,name,count FROM production WHERE id=2";
resultSet = statement.executeQuery(querySql);
int count = 0;
while (resultSet.next()) {
System.out.println(Thread.currentThread().getName() + "搶到了鎖 id: " + resultSet.getString("id")
+ " name: " + resultSet.getString("name")
+ " count: " + resultSet.getString("count"));
count = Integer.valueOf(resultSet.getString("count"));
}
String updateSql = "UPDATE production SET count=" + (count - 1)
+ " WHERE id=2";
int rows = statement.executeUpdate(updateSql);
if (rows > 0) {
System.out.println("更新成功" + Thread.currentThread().getName() + " 庫存剩余:" + (count - 1));
System.out.println(Thread.currentThread().getName() + " === > >開始解鎖");
boolean unlock = unlock(jedis, lock);
if (unlock)
System.out.println(Thread.currentThread().getName() + " === > >解鎖成功");
} else {
System.out.println("更新失敗" + Thread.currentThread().getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null)
conn.close();
if (statement != null)
statement.close();
if (resultSet != null)
resultSet.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}, "線程" + count).start();
}
}
public static String lock(Jedis jedis) {
try {
while (true) {
String lockTime = Long.valueOf(jedis.time().get(0)) + 5 + "";
if (jedis.setnx("lock", lockTime) == 1) {
jedis.expire("lock", 5);
return lockTime;
}
String lock = jedis.get("lock");
if (!StringUtils.isEmpty(lock) && Long.valueOf(lock) < Long.valueOf(jedis.time().get(0))) {
String oldLockTime = jedis.getSet("lock", lockTime);
if (!StringUtils.isEmpty(oldLockTime) && oldLockTime.equals(lock)) {
return lockTime;
}
}
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static boolean unlock(Jedis jedis, String lockTag) {
if (lockTag.equals(jedis.get("lock"))) {
jedis.del("lock");
return true;
}
return false;
}
}
運行結果如下圖:

更多學習內容請訪問:
騰訊T3-T4標准精品PHP架構師教程目錄大全,只要你看完保證薪資上升一個台階(持續更新)
