同步缓存的项目

时间:2015-12-12 04:27:03

标签: java multithreading caching guava

我使用

之类的东西
Cache<Integer, Item> cache;

其中Item s彼此独立,看起来像

private static class Item {
    private final int id;
    ... some mutable data

    synchronized doSomething() {...}
    synchronized doSomethingElse() {...}
}

这个想法是从缓存中获取项目并在其上调用synchronized方法。如果遗漏,可以重新创建项目,这很好。

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

有一个简单的方法吗?它是Guava Cache,如果有帮助的话。

2 个答案:

答案 0 :(得分:2)

我认为Louis的建议,使用锁定键是最简单实用的。这是代码一些片段,没有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正在实施的概念。通过指定一个可以撤销的驱逐政策,您可以自己实施固定机制。

答案 1 :(得分:1)

我确信您的问题有多种解决方案。 我写下了其中一个,为每个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");
    }
}

}

因此,不要在类Item中使用 synchronized 方法,而是使用LockManager通过itemId获取Lock,并在检索后调用lock.lock()。 另请注意,LockManager应具有单例范围,并且应在所有用途中共享相同的实例。

下面您可以看到LockManager的示例:

            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();
        }