以前刚开始实现时,想当然的用ConcurrentHashMap的putIfAbsent和ReentrantLock来实现lock by key,但是一到后面仔细想想,发现有很多漏洞和麻烦的地方。
首先是错误的代码
Map<String, Lock> keyLockMap = new ConcurrentHashMap();
final Lock keyLock = keyLockMap.computeIfAbsent(k, k -> new ReentrantLock()); //1
keyLock.lock(); //2
try{
//
} finally {
keyLock.unlock();
keyLockMap.remove(username); //3
}
会发生问题的地方
假设分别有3个线程现在运行到上述1,2,3位置处,那么3线程释放后,1获取的keyLock和2是两个不同的Lock对象,这里就会锁不住。
其实要考虑到资源释放,就没上面这边简单,起码还需要一个锁来保证资源的释放不和获取冲突。
网上搜索的简单的解决方式
hash分段锁
预先分配一批锁对象,利用hash索引去获取锁,这样就没有上面那个错误代码的问题了
int concurrencyLevel = 1 << 8; // 256 locks
final Lock[] locks = new Lock[concurrencyLevel];
// initialize locks
void doSomething(String id) {
Lock lock = locks[Math.abs(id.hashCode()) % concurrencyLevel]; // select one of 256 locks
lock.lock();
try {
// do some work
} finally {
lock.release();
}
}
现有的工具解决方法
Google Guava Striped
//init
stripes=Striped.lazyWeakLock(size);
//or
stripes=Striped.lock(size);
//...
Lock lock=stripes.get(object);
最后
以上都是堆内的解决方案,如果涉及到分布式,需要使用分布式的解决方案,比如Redisson
Q.E.D.