我有一个关于锁upgrading.Specifically的问题,困扰我的是readlock.unlock()和writelock.lock().我正在为一个示例缓存提供一个几乎完全的实现,我只是省略了从数据库中实际加载缓存的数据。
我很感谢你能回顾一下代码并分享你的想法。我试图在java注释中表达我的关切。我的问题是如何正确同步以避免重新检查加载缓存中的缓存。
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class JavaRanchSampleCache {
private ConcurrentHashMap<String, ReentrantReadWriteLock> refreshLocks = new ConcurrentHashMap<String, ReentrantReadWriteLock>();
private ConcurrentHashMap<String, HashMap<String, String>> cacheData = new ConcurrentHashMap<String, HashMap<String, String>> ();
public HashMap<String, String> getCachedData(String cacheKey)
{
ReentrantReadWriteLock lock = refreshLocks.get(cacheKey);
if(lock==null)
{
lock=new ReentrantReadWriteLock();
ReentrantReadWriteLock previous=refreshLocks.putIfAbsent(cacheKey, lock);
if(previous!=null)//null means no previous lock object,first time usage
lock=previous;
}
//we have a safe lock at this point
try
{
lock.readLock().lock();//read the cached item for correspoding cacheKey
HashMap<String, String> cachedItem=cacheData.get(cacheKey);
if(cachedItem==null)//it is not cached yet.Load to cache on first request
{
cachedItem=loadItemExpensive(cacheKey);
}
return cachedItem;//return the cached item.
}
finally
{
lock.readLock().unlock();
}
}
private HashMap<String, String> loadItemExpensive(String cahceKey)
{
ReentrantReadWriteLock lock = this.refreshLocks.get(cahceKey);
HashMap<String, String> cachedItem=null;
try
{
/*these two lines are for lock upgrading
* BUT what happens if another thread interacts between line1 and line2
* I mean after the readlock is relased,some OTHER thread might gain the writelock
* before this thread and perform loaddata and update cache operation.
* So should I check the cache once more to see if some other thread
* has updated the cache? I 'feel' that I should,but it is ugly,i need some clever way.
*/
lock.readLock().unlock();//line1
lock.writeLock().lock();//line2
/*omitted load data from some expensive store such as database or remote server*/
return cachedItem;
}
finally
{
lock.readLock().lock();//writelock owner can grant readlock immediately.
lock.writeLock().unlock();//lock released
}
}
}发布于 2012-06-20 17:33:44
你的恐惧是有道理的。一旦你松开锁,所有的赌注都取消了。当您获得写锁时,您将需要检查另一个线程是否改变了缓存。不要认为它是“丑陋的”,认为它是“正确的”。继续使用注释来解释你在做什么,以及为什么你要这么做。
简而言之,读/写锁不能同时支持升级和降级。必须始终以相同的顺序获得多个锁(在这种情况下,写然后读取),否则就会发生致命的拥抱。因此,当选择另一种方式时(读然后写),您必须释放锁,此时必须重新考虑基于当前状态所做的任何决定。
但是还有其他的选择,取决于你的具体情况。
一种是总是获得一个写锁,以便在数据已经存在的情况下进行修改,并将其降级为读锁。这可能不是您想要的,特别是在大多数操作都是只读的情况下,因为它会导致锁定争用和负载下的不良响应时间。
其他选项可能包括允许多个线程(冗余地)执行昂贵的负载,以及在插入表时获取写锁。或者只是盲目地覆盖现有条目(在最后一种情况下,您真正需要的是ConcurrentHashMap)。这完全取决于你愿意容忍什么情况。
希望这能有所帮助。
发布于 2013-09-13 12:01:27
这是一个老问题,但这里有一个问题的解决方案,以及一些背景信息。
如果您需要在不首先释放读锁的情况下安全地获取写锁,请查看另一种类型的锁,即读-写-__update锁。
我编写了一个ReentrantReadWrite_Update_Lock,并在Apache2.0许可这里下作为开放源码发布了它。我还向JSR166 并发-利息邮件列表发布了有关该方法的详细信息,该方法通过了名单上成员的反复审查。
这个方法非常简单,而且正如我在并发感兴趣方面所提到的,这个想法并不是完全新的,因为Linux内核邮件列表至少早在2000年就已经讨论过了。此外,.Net平台的ReaderWriterLockSlim也支持锁升级。因此,这个概念到目前为止还没有在Java (AFAICT)上实现。
其想法是除了读锁和写锁之外,还提供更新锁。更新锁是读锁和写锁之间的一种中间类型的锁。与写锁一样,一次只能有一个线程获得更新锁。但就像读锁一样,它允许对持有它的线程进行读取访问,并并发访问持有常规读锁的其他线程。关键的特性是,更新锁可以从只读状态升级到写锁,这不会受到死锁的影响,因为只有一个线程可以保存更新锁,并且一次可以升级。
这支持锁升级,而且它比传统的读写器锁在具有读写访问模式的应用程序中更有效,因为它阻塞读取线程的时间更短。
在站点上提供了示例用法。该库具有100%的测试覆盖率,并且位于Maven中心。
注意,有一个类似的问题,这里。
https://codereview.stackexchange.com/questions/12939
复制相似问题