Java 在缓存项上同步

Java 在缓存项上同步,java,multithreading,caching,guava,Java,Multithreading,Caching,Guava,我用的是 Cache<Integer, Item> cache; 其思想是从缓存中获取该项并对其调用同步方法。如果未命中,可以重新创建项目,这很好 当线程运行同步方法时,从缓存中逐出项并重新创建时,会出现问题。一个新线程获取一个新项目并在其上同步。。。因此,对于单个id,同步方法中有两个线程。失败 有没有简单的办法?如果有帮助的话,这是非常重要的。我相信有多种解决方案可以解决您的问题。 我用每个ietmId的唯一锁写下了其中一个: public class LockManager

我用的是

Cache<Integer, Item> cache;
其思想是从缓存中获取该项并对其调用同步方法。如果未命中,可以重新创建项目,这很好

当线程运行同步方法时,从缓存中逐出项并重新创建时,会出现问题。一个新线程获取一个新项目并在其上同步。。。因此,对于单个
id
,同步方法中有两个线程。失败


有没有简单的办法?如果有帮助的话,这是非常重要的。

我相信有多种解决方案可以解决您的问题。 我用每个ietmId的唯一锁写下了其中一个:

public class LockManager {

private Map<Integer, Lock> lockMap = new ConcurrentHashMap<>();

public synchronized Lock getOrCreateLockForId(Integer itemId) {
    Lock lock;
    if (lockMap.containsKey(itemId)) {
        System.out.println("Get lock");
        lock = lockMap.get(itemId);
    } else {
        System.out.println("Create lock");
        lock = new ReentrantLock();
        lockMap.put(itemId, lock);
    }
    return lock;
}

public synchronized Lock getLockForId(Integer itemId) {
    Lock lock;
    if (lockMap.containsKey(itemId)) {
        System.out.println("get lock");
        return lockMap.get(itemId);
    } else {
        throw new IllegalStateException("First lock, than unlock");
    }
}

我认为路易斯的建议,用钥匙锁是最简单和实用的。下面是一些代码片段,在没有Guava库的帮助下,它说明了这个想法:

static locks[] = new Lock[ ... ];
static { /* initialize lock array */ } 
int id;
void doSomething() {
  final lock = locks[id % locks.length];
  lock.lock();
  try {
    /* protected code */
  } finally {
    lock.unlock();
  } 
}
锁数组的大小限制了获得的最大并行度。如果您的代码只使用CPU,您可以根据可用处理器的数量对其进行初始化,这是一个完美的解决方案。如果代码等待I/O,则可能需要一个任意大的锁数组,或者限制可以运行关键部分的线程数。在这种情况下,另一种方法可能更好

在概念层面上的评论:

如果要防止项目被逐出,则需要一种称为固定的机制。在内部,这被大多数缓存实现所使用,例如用于I/O操作期间的阻塞。某些缓存可能会公开应用程序执行此操作的方法

在与JCache兼容的缓存中,有EntryProcessor的概念。EntryProcessor允许您以原子方式处理条目上的代码和平。这意味着缓存正在为您执行所有锁定。根据问题的范围,这可能具有优势,因为这也适用于集群场景,这意味着锁定是集群范围的


我想到的另一个想法是可否决的驱逐。这是EHCache 3正在实施的一个概念。通过指定可否决逐出策略,您可以自行实现锁定机制。

我可能会尝试使用单独的
条带化
来获取每键锁?如果要更新的项数与缓存中的项总数相比较少,并且如果项对象中的可变数据不是太大,那么同步如何深度克隆Item对象,然后自动更新缓存中相应Item中的可变数据。Guava的版本将使用零权重,由于大小限制,使得条目不合格。Ehcache的否决权只是一个暗示,可能不会得到尊重,因此它的合同有点令人惊讶。@BenManes问题是“在将条目插入缓存时会测量和记录权重,因此在缓存条目的生命周期内,权重实际上是静态的。”,而我需要在锁定期间进行固定。@maaartinus抱歉,我只是回应否决权在番石榴中的作用,而不是提出解决你的问题的建议。您的问题感觉就像用户在使用缓存作为对象池(读取时独占访问资源)时面临的问题。您可能需要寻找一个“键控对象池”库,但存在累积无限数量锁的问题。在许多情况下可能可以接受……在执行解锁后,从地图中移除锁是有意义的。我只是想告诉你一个方法。它可能需要在实际应用中添加一些更改
            try {
            lockManager.getOrCreateLockForId(itemId).lock();
            System.out.println("start doing something" + num);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("completed doing something" + num);
        } finally {
            lockManager.getLockForId(itemId).unlock();
        }
static locks[] = new Lock[ ... ];
static { /* initialize lock array */ } 
int id;
void doSomething() {
  final lock = locks[id % locks.length];
  lock.lock();
  try {
    /* protected code */
  } finally {
    lock.unlock();
  } 
}