前言
相信在互联网公司工作的各位小伙伴都深刻认识到,当不同进程操作同一个对象的时候都要保证原子性,这时候就会用到锁,但是在同一个进程里修改可以用sychronized、LockCondition来加锁。为了能在分布式使用锁,我们一般都是用Redis来实行,当然也有其他解决方案啦。
具体流程可以看下图:
图中可以看到几个步骤:
- 多台主机(这里也可以叫服务器)向Redis申请锁。
- 获取锁成功后会返回OK,失败的话就返回null。
- 获取锁的进程就可以往下执行业务。
当然可能有些小伙伴就会有疑问,如果同一时间有多个进程申请锁,不会引起并发问题,例如多个进程都获取到同一个锁?这个其实不用担心,因为Redis是单线程的,单线程怎么会出现并发问题?至于为什么单线程,各位可以去上网搜下Redis机制,或者我后期也会出一篇文章来讲解Redis之所快的原因。
其实看上去也不是特别难,难的部分Redis都做好了,所以大家没必要畏惧。
实现
我比较倾向于用set方法,其实我个人不太建议用setnx,.最主要的原因是setnx不能设置有效时间,不能设置有效时间意味着什么,看代码!(提示下,我这里用的Jedis,包括后续的代码)
public void setnxWithTimeOut(String key, String value, int timeOut) {
Jedis jedis = getJedis();
if (0 != jedis.setnx(key, value)) {
// 如果运行到这里突然发生宕机,那么锁就永远不会释放
jedis.expire(key, timeOut);
}
}
好了好了,我觉得我扯太多了。接下来是进入正题,还是老规矩,继续看代码:
/**
* 分布式锁
* @param key
* @param value
* @param nx
* @param expx
* @param time
* @return
*/
public String set(String key, String value, String nx, String expx, int time){
Jedis jedis = getJedis();
String result = jedis.set(key, value, nx, expx, time);
return result;
}
这里解释下各个参数的意思:
- key是设置锁的key值。
- value是这个锁的值,这个value值你可以设置为线程ID。
- nx只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set。
- expx只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒。
- time就是过期时间。
实验:
/**
* 停滞状态超过两天自动更换为公海状态, 每天晚上1点执行
*/
private static final String LOCK_NX = "NX";
private static final String LOCK_EXPRIETYPE = "EX";
private static final String LOCK_KEY = "LockForChangeState";
private static final String LOCK_SUCCESS = "OK";
public void changeStatusStopToHighSea() {
// 加锁成功返回OK字符串,否则返回null
String lock = jedisUtil.set(LOCK_KEY, String.valueOf(Thread.currentThread().getId()), LOCK_NX, LOCK_EXPRIETYPE, 600);
if (LOCK_SUCCESS.equals(lock)) {
// 下面写需要执行的业务流程
System.out.println("123");
}
}
释放锁的话我目前知道有两种:
- 等有效期过去,这种一般不太好,感觉这种方式有点别扭,业务流程执行完不释放锁就是占着茅坑不拉屎,这样会影响后面想用厕所的人,可不好。如果人少另说,
- 移除对应的key,这个比较简单粗暴。
结论
用的redis越多发现它真是太强大,做什么都可以,分布式锁、缓存数据库、权限认证等等。所以啊,这么强大的组件,我还是建议各位兄弟去看下,虽然是由C语言编写哈哈。