(1)关键点
1.不管读还是写,都存在操作成功、操作失败、超时三种情况
2.写的时候要依赖缓存的乐观锁,避免多次被修改,通常就是版本号
3.因为存在一个线程获得锁之后突然崩溃,导致分布式锁无法释放的情况,缓存值必须要有过期时间
(2)实现
1.缓存接口
interface TairManager{
/**
* 读数据
* @param key
* @return
*/
public Result get(String key);
/**
* 写数据
* @param key
* @param lockValue
* @param defaultVersion 版本号。如果之前key值不存在,则version任意即可;如果存在,则version必须相等(相当于乐观锁),更新后version自动加一
* @param defaultExpiredtimeInSecond
* @return
*/
public Result put(String key, String lockValue, int defaultVersion,
int defaultExpiredtimeInSecond);
}
2.分布式锁
public class DistributionLock {
private TairManager tairManager;
private static final int MAX_RETRY = 3;
private static final int CODE_DATA_EXIST = 0;
private static final int CODE_DATA_NOT_EXIST = 1;
private static final int CODE_WRITE_SUCCESS = 2;
private static final int CODE_WRITE_VERSION_ERROR = 3;
private static final int DEFAULT_VERSION = 99;
private static final int DEFAULT_EXPIREDTIME_IN_SECOND = 10*60;
public boolean lock(String key){
/**
* 尝试读缓存数据
*/
Result queryResult = null;
int readRetry = 0;
while (readRetry++<MAX_RETRY) {
queryResult = tairManager.get(key);
if (queryResult.isSuccess() && queryResult.getCode()==CODE_DATA_EXIST) { //如果数据存在
return false;
}else if (queryResult.isSuccess() && queryResult.getCode()==CODE_DATA_NOT_EXIST) { //如果数据不存在
break;
}else { //如果出现超时等问题
continue;
}
}
/**
* 读接口一直超时
*/
if (queryResult==null) { //说明重试读了多次都没有返回值
throw new RuntimeException();
}
/**
* 如果数据不存在,则尝试自己去写
*/
if (queryResult.getCode()==CODE_DATA_NOT_EXIST) { //如果数据不存在
int writeRetry = 0;
while (writeRetry++<MAX_RETRY) {
Result writeResult = tairManager.put(key,getlockValue(),DEFAULT_VERSION,DEFAULT_EXPIREDTIME_IN_SECOND);
if (writeResult.isSuccess() && writeResult.getCode()==CODE_WRITE_SUCCESS) { //如果写成功则说明获得了锁
return true;
}else if (writeResult.isSuccess() && writeResult.getCode()==CODE_WRITE_VERSION_ERROR) {
/**
* 如果因为版本问题写失败,则说明已经被改过了,那就需要进一步判断是否是当前线程写的
*/
int confirmRetry = 0;
while (confirmRetry++<MAX_RETRY) {
Result confirmResult = tairManager.get(key);
if (confirmResult.isSuccess() && confirmResult.getCode()==CODE_DATA_EXIST) { //如果数据存在
String value = confirmResult.getValue();
if (getlockValue().equals(value)) { //如果缓存中值是当前线程的指纹,说明上次就是当前线程修改的,只是因为超时没收到成功的返回值
return true;
}else { //已经被其他线程获得了
return false;
}
}else { //如果出现超时等问题
continue;
}
}
//如果重试读了多次还是没结果返回
throw new RuntimeException();
}else { //如果是超时等原因出错,则重试写
continue;
}
}
//如果重试写了多次还是没结果返回
throw new RuntimeException();
}
return false;
}
/**
* 获取当前线程的指纹,用来标记锁是否是由当前线程获得的
* @return
*/
private String getlockValue(){
return "uuid";
}
}
一定注意:如果是因为版本问题写失败,要判断一下是否是当前线程上一次写的