环境 四台tomcat作为集群,redis作为共享内存。
需求 对于一个共享状态的改变进行加锁,锁的属性为互斥且可重入
实现 仿照ReentrantLock类写一个分布式锁
问题 CAS实现
解决 使用redis乐观锁实现CAS
CAS(compare and swap)
比较并交换;在替换新值前比较一下旧值是否为传入的值,如果一样则替换,当然这样会产生ABA问题,即当原值为A,随后变成了B接着又变成了A,那么进行比较时发现值没有改变,其实已经改变了,解决方法则是为值添加上版本号。
使用redis事务实现乐观锁请查看《利用redis乐观锁实现tomcat集群抢占定时任务》,该文前小半部分是乐观锁的实现。
代码实现
利用redis创建一个hash,里面有三个值,state(状态)值为0,1,2,…;exclusiveownerthreadname(占有线程名)值为唯一的名称,可以IP+线程名或者IP+UUID+线程名;nouse(无用)值任意,为实现判断乐观锁设置的字段,无实际用处,下文有解释。本段代码只使用了state和nouse两个字段。
/**
* 比较并设置值 利用乐观锁实现CAS
* @param jedis redis连接
* @param key 锁的key
* @param oldState 比较值
* @param newState 替换值
* @return
*/
private boolean compareAndSetState(Jedis jedis, String key, int oldState, int newState) {
boolean retBoolean;
while (true) {
retBoolean = false;
try {
jedis.watch(key);
Transaction tran;
List result = null;
if (jedis.exists(key)) {
int state = Integer.parseInt(jedis.hget(key, ConcurrentGlobal.PUBLIC_DISTRIBUTEDREENTRANTLOCK_STATE));
tran = jedis.multi();
if (state == oldState) {
retBoolean = true;
tran.hset(key, ConcurrentGlobal.PUBLIC_DISTRIBUTEDREENTRANTLOCK_STATE, String.valueOf(newState));
} else {
// 随便保存一个key保持事务中有一个任务
tran.hset(key, ConcurrentGlobal.PUBLIC_DISTRIBUTEDREENTRANTLOCK_NOUSE, "1");
}
} else {
tran = jedis.multi();
retBoolean = true;
tran.hset(key, ConcurrentGlobal.PUBLIC_DISTRIBUTEDREENTRANTLOCK_STATE, String.valueOf(newState));
}
result = tran.exec();
if (result != null && !result.isEmpty()) {
break;
}
} finally {
jedis.unwatch();
}
}
return retBoolean;
}
该段代码并没有解决ABA问题。
代码解析
个人理解其实乐观锁和CAS很相似,都是原子性实现某些操作。