2010-09-21 66 views
0

我已经阅读了几个与此类似的问题,但没有一个答案提供了如何在保持锁完整性的同时清理内存的想法。我估计在给定时间键值对的数量是成千上万,但是在数据结构的整个生命周期中键值对的数量实际上是无限的(实际上它可能不会更多超过十亿,但我正在编码到最坏的情况)。锁定缓存键

我有一个接口:

public interface KeyLock<K extends Comparable<? super K>> { 

    public void lock(K key); 

    public void unock(K key); 

} 

有一个默认的实现:

public class DefaultKeyLock<K extends Comparable<? super K>> implements KeyLock<K> { 

    private final ConcurrentMap<K, Mutex> lockMap; 

    public DefaultKeyLock() { 
    lockMap = new ConcurrentSkipListMap<K, Mutex>(); 
    } 

    @Override 
    public void lock(K key) { 
    Mutex mutex = new Mutex(); 
    Mutex existingMutex = lockMap.putIfAbsent(key, mutex); 
    if (existingMutex != null) { 
     mutex = existingMutex; 
    } 
    mutex.lock(); 
    } 

    @Override 
    public void unock(K key) { 
    Mutex mutex = lockMap.get(key); 
    mutex.unlock(); 
    } 

} 

这工作得很好,但地图上永远不会被清理。我至今一个干净的实现是:

public class CleanKeyLock<K extends Comparable<? super K>> implements KeyLock<K> { 

    private final ConcurrentMap<K, LockWrapper> lockMap; 

    public CleanKeyLock() { 
    lockMap = new ConcurrentSkipListMap<K, LockWrapper>(); 
    } 

    @Override 
    public void lock(K key) { 
    LockWrapper wrapper = new LockWrapper(key); 
    wrapper.addReference(); 
    LockWrapper existingWrapper = lockMap.putIfAbsent(key, wrapper); 
    if (existingWrapper != null) { 
     wrapper = existingWrapper; 
     wrapper.addReference(); 
    } 

    wrapper.addReference(); 
    wrapper.lock(); 
    } 

    @Override 
    public void unock(K key) { 
    LockWrapper wrapper = lockMap.get(key); 
    if (wrapper != null) { 
     wrapper.unlock(); 
     wrapper.removeReference(); 
    } 
    } 

    private class LockWrapper { 

    private final K key; 

    private final ReentrantLock lock; 

    private int referenceCount; 

    public LockWrapper(K key) { 
     this.key = key; 
     lock = new ReentrantLock(); 
     referenceCount = 0; 
    } 

    public synchronized void addReference() { 
     lockMap.put(key, this); 
     referenceCount++; 
    } 

    public synchronized void removeReference() { 
     referenceCount--; 
     if (referenceCount == 0) { 
     lockMap.remove(key); 
     } 
    } 

    public void lock() { 
     lock.lock(); 
    } 

    public void unlock() { 
     lock.unlock(); 
    } 
    } 

} 

这适用于两个线程访问单个按键锁定功能,但一旦第三线程引入了锁完整性不再保证。有任何想法吗?

+0

忽略多余addReference()在锁定方法调用。我在尝试一些想法,并忘记在发布代码时将它们带出。 – user453385 2010-09-21 02:14:09

回答

0

我不买,这适用于两个线程。试想一下:

  • (线程A)调用lock(x)的,现在持有锁X
  • 线程切换
  • (线程B)调用锁(X)的putIfAbsent()返回x的当前包装
  • 线程切换
  • (线程A)调用解锁(X),则包装器引用计数击中0和它得到从地图中删除
  • (线程A)调用锁(X),的putIfAbsent()插入一个新包装为x
  • (线程A)在新的包装锁
  • 线程切换
  • (线程B)上的旧包装锁

如何:

  • LockWrapper为1的引用计数开始
  • 如果引用计数为0,则addReference()返回false。
  • in lock(),如果existingWrapper!= null,则对其调用addReference()。如果返回false,它已被从地图上删除,因此,我们回送,并从的putIfAbsent()
+0

您在一般情况下是正确的。我喜欢重新尝试putIfAbsent的想法,特别是因为我可以通过比较和交换操作来实现它。 – user453385 2010-09-22 13:05:42

0

我会用一定的排列默认的条纹锁定再试一次,因为你可以它的大小到您期望的并发级别。虽然可能会有散列冲突,但一个好的散布器可以解决这个问题。如果这些锁用于较短的关键部分,那么您可能会在ConcurrentHashMap中创建争用,导致优化失败。

欢迎您适应我的实现,尽管我只实现了动态版本以获得乐趣。它在实践中似乎没有用处,所以只有固定用于生产。您可以使用ConcurrentHashMap中的hash()函数来提供良好的传播。

ReentrantStripedLock中, http://code.google.com/p/concurrentlinkedhashmap/wiki/IndexableCache