Cache<K,BlockingDeque<V>>与Striped相结合<Lock>:如何锁定整个缓存



我有这样的东西:

private Striped<ReadWriteLock> stripes = Striped.lazyWeakReadWriteLock(STRIPES_AMOUNT);
private final Cache<Long, BlockingDeque<Peer>> peers = CacheBuilder.newBuilder()
    .expireAfterWrite(PEER_ACCESS_TIMEOUT_MIN, TimeUnit.MINUTES)
    .build();

每次我对缓存进行操作时,我都会在stripes的帮助下锁定它。

示例#1 [写入操作]

public void removePeers(long sessionId) {
    Lock lock = stripes.get(sessionId).writeLock();
    lock.lock();
    try {
        peers.invalidate(sessionId);
    } finally {
        lock.unlock();
    }
}

示例#2 [读取操作]

public BlockingDeque<Peer> getPeers(long sessionId) {
    Lock lock = stripes.get(sessionId).readLock();
    lock.lock();
    try {
        return peers.getIfPresent(sessionId);
    } finally {
        lock.unlock();
    }
}

示例#3 [写入操作]

public boolean addPeer(Peer peer) {
    long key = peer.getSessionId();
    Lock lock = stripes.get(key).writeLock();
    lock.lock();
    try {
        BlockingDeque<Peer> userPeers = peers.getIfPresent(key);
        if (userPeers == null) {
            userPeers = new LinkedBlockingDeque<Peer>();
            peers.put(key, userPeers);
        }
        return userPeers.offer(peer);
    } finally {
        lock.unlock();
    }
}

问:锁定以下方法的最有效方法是什么?

/**
 * I should get the whole peers in cache
 */
public BlockingDeque<Peer> getAllPeers() {
    BlockingDeque<Peer> result = new LinkedBlockingDeque<Peer>();
    for (BlockingDeque<Peer> deque : peers.asMap().values()) {
        result.addAll(deque);
    }
    return result;
}

最有效的方法是完全不锁定:)

请参阅我在代码审查中更新的答案:您不需要ReadWriteLock,因此您无需锁定读取。

当您读取(例如在线程 1 中)时,您将获得当时缓存中内容的快照。如果您有并发修改(在线程 2 中),即使您使用锁,缓存的内容也可能在线程 1 完成其计算之前发生了变化,并且锁不会为您购买任何东西:

    Thread 1             Thread 2
       |                    |
   getAllPeers              |
       |                 addPeer
Do something with           |
the Peers but not           |
  the added one             |
       |                    |

因此,您对getAllPeers()的实现很好。


作为旁注,如果您需要将所有条纹锁定在一个 Striped<Lock> 中,您可以使用 getAt() 来完成,但如果其中一个lock()调用抛出未经检查的异常(这是允许的),这种天真的方式可能会给您带来麻烦:

for (int i = 0, size = stripes.size(); i++; i < size) {
    stripes.getAt(i).lock();
}
try {
    // Do something
} finally {
    for (int i = 0, size = stripes.size(); i++; i < size) {
        stripes.getAt(i).unlock();
    }
}

另一种方法是递归执行此操作,但它会增加堆栈的长度条带数,因此如果您有大量条带,则可以获得StackOverflowException

public void doSomethingWithLocks() {
     doSomethingWithLock(0);
}
private void doSomethingWithLock(int stripe) {
     if (stripe < stripes.size()) {
         Lock lock = stripes.getAt(stripe);
         lock.lock();
         try {
             doSomethingWithLock(stripe + 1);
         } finally {
             lock.unlock();
         }
     } else {
         doSomething();
     }
}
private void doSomething() {
    // Do something
}

最新更新