架构-轻松搞懂锁和分布式锁

版权声明:fromZjy QQ1045152332 https://blog.csdn.net/qq_36762677/article/details/84931777

为什么使用锁:

多线程环境中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开发者必须考虑如何维护数据一致性,这就需要某种机制来保证只有满足某个条件(获取锁成功)的线程才能访问资源,而不满足条件(获取锁失败)的线程只能等待,在下一轮竞争中来获取锁才能访问资源。

什么是分布式锁?

  1. 线程锁
    主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如Synchronized、Lock等。
  2. 进程锁
    为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
  3. 分布式锁
    实现不同机器对同一资源的加锁,当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

分布式锁的特点

1、互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。
2、安全性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。
3、死锁:获取锁的客户端因为某些原因(如down机等)而未能释放锁,其它客户端再也无法获取到该锁。
4、容错:当部分节点(redis节点等)down机时,客户端仍然能够获取锁和释放锁。

分布式锁(redis的简单实现)

  1. 数据库乐观锁;
  2. 基于Redis的分布式锁;
  3. 基于ZooKeeper的分布式锁

1.加锁

最简单的方法是使用setnx命令。key是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加锁,可以给key命名为 “lock_sale_商品ID” 。而value设置成什么呢?我们可以姑且设置成1。加锁的伪代码如下:

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

时间复杂度:O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败。

2.解锁

有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行del指令,伪代码如下:
del(key)
释放锁之后,其他线程就可以继续执行setnx命令来获得锁。

3.设置超时时间

如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。

所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。setnx不支持超时参数,所以需要额外的指令,伪代码如下:
expire(key, 30)
综合起来,我们分布式锁实现的第一版伪代码如下:

if(setnx(key,1) == 1){
 expire(key,30)
 do something ......
 del(key)
 }

redis 分布式请参考其他文章

https://blog.csdn.net/qq_36762677/article/details/82880239


代码区

private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
 /**
 * 尝试获取分布式锁
 * @param jedis Redis客户端
 * @param lockKey 锁
 * @param requestId 线程Id
 * @param expireTime 超期时间
 * @return 是否获取成功
 */
 public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
	 String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
	 if (LOCK_SUCCESS.equals(result)) {
	 	return true;
	 }
	 return false;
 }
private static final Long RELEASE_SUCCESS = 1L;
 /**
 * 释放分布式锁
 * @param jedis Redis客户端
 * @param lockKey 锁
 * @param requestId 请求标识
 * @return 是否释放成功
 */
 public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
	 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
	 Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
	 if (RELEASE_SUCCESS.equals(result)) {
	 	return true;
	 }
	 return false;
 }

https://www.cnblogs.com/garryfu/p/7978611.html

猜你喜欢

转载自blog.csdn.net/qq_36762677/article/details/84931777