首先是互斥锁。
优点:能够根据请求的key加锁,提高了并发性。
缺点:GC不友好,不是分布式的。
public final class MutexLock {
private static final ConcurrentHashMap<String,CountDownLatch> locks=new ConcurrentHashMap<>();
public static boolean tryLock(String key){
//能过滤掉后持有相同key的线程
return !locks.containsKey(key);
}
public static void lock(String key){
//多线程持有相同的key并发执行时,可能同时调用 {@code lock(key)},但只有一个线程能成功
//并发时阻塞的线程只能等着,其他持有相同key的线程可能通过{@code tryLock(key)}判断后去做其他的事情
//对GC不友好,因为会new很多CountDownLatch,但不会超多最大线程数。
while(locks.putIfAbsent(key,new CountDownLatch(1))!=null){
CountDownLatch lock=locks.get(key);
try {
lock.await(50, TimeUnit.MILLISECONDS);
}catch (InterruptedException e){
//处理异常
}
}
}
public static void unLock(String key){
CountDownLatch lock=locks.remove(key);
lock.countDown();
}
}
借助互斥锁解决缓存击穿问题:
public String getVal(String key){
//一级缓存,取到直接返回
String val=getValFromRedis(key);
if(getValFromRedis(key)==null){
if(MutexLock.tryLock(key)){
try {
MutexLock.lock(key);
val=getValFromRedis(key);
if (val==null){
val=getValFromMysql(key);
}
//可以打日志看一下更新的值
if (val!=null){
updateCache(key,val);
}
}finally {
MutexLock.unLock(key);
}
}else{
//二级缓存取
val=getValFromHbase(key);
//还取不到,重试
if(val==null){
//重试次数可以配在zk里面
int i=3;
while(i>0){
val=getVal(key);
if(val!=null){
break;
}
i--;
}
//取不到,重试还是取不到
if(val==null){
//发报警邮件,或记录在什么地方
}
}
}
}
return val;
}