我使用
之类的东西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,如果有帮助的话。
答案 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();
}