使用分布式锁要满足的几个条件:
系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现)
共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者NoSQL)
同步访问(即有很多个进程同时访问同一个共享资源)
什么是分布式锁?
线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
应用的场景
有这样一个情景,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
这里主要讲解如何用redis实现分布式锁
使用redis的setNX命令实现分布式锁
实现的原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令可以方便的实现分布式锁。
基本命令解析
1)setNX (SET if Not Exists)
语法:
SETNX key value
将 key的值设为value,当且仅当key不存在。
若给定的key已经存在,则SETNX不做任何动作。
SETNX是【SET if Not eXists】(如果不存在,则SET)的简写
返回值:
设置成功,返回1
设置失败,返回0
栗子:
192.168.199.128:6379> EXISTS job # job 不存在(integer) 0192.168.199.128:6379> SETNX job "programmer" # job 设置成功(integer) 1192.168.199.128:6379> SETNX job "code-farmer" # 尝试覆盖 job ,失败(integer) 0192.168.199.128:6379> GET job # 没有被覆盖"programmer"
客户端可以尝试以下方式:
SETNX lock.foo <current Unix time + lock timeout + 1>
GETSET key value
同一时刻只能有一个进程获取到锁。
sentx
释放锁:
锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
(最简单的方式就是del,如果在删除之前死锁了)

public static boolean lock(String lockName) {Jedis jedis = RedisPool.getJedis();//lockName可以为共享变量名,也可以为方法名,主要是用于模拟锁信息System.out.println(Thread.currentThread() + "开始尝试加锁!");Long result = jedis.setnx(lockName, String.valueOf(System.currentTimeMillis() + 5000));if (result != null && result.intValue() == 1){System.out.println(Thread.currentThread() + "加锁成功!");jedis.expire(lockName, 5);System.out.println(Thread.currentThread() + "执行业务逻辑!");jedis.del(lockName);return true;} else {//判断是否死锁String lockValueA = jedis.get(lockName);//得到锁的过期时间,判断小于当前时间,说明已超时但是没释放锁,通过下面的操作来尝试获得锁。下面逻辑防止死锁[已经过期但是没有释放锁的情况]if (lockValueA != null && Long.parseLong(lockValueA) < System.currentTimeMillis()){String lockValueB = jedis.getSet(lockName,String.valueOf(System.currentTimeMillis() + 5000));//这里返回的值是旧值,如果有的话。之前没有值就返回null,设置的是新超时。if (lockValueB == null || lockValueB.equals(lockValueA)){System.out.println(Thread.currentThread() + "加锁成功!");jedis.expire(lockName, 5);System.out.println(Thread.currentThread() + "执行业务逻辑!");jedis.del(lockName);return true;} else {return false;}} else {return false;}}}




