我想不出一种方法来真正捕捉您锁定一对对象的想法。一些低级并发研究人员可能能够发明一个,但我怀疑我们是否有必要的原语在 Java 中实现它。
我认为使用对作为键来识别锁定对象的想法是一个很好的想法。如果您想避免锁定,请安排查找,使其不执行任何操作。
我建议使用两级地图,大致如下:
Map<DatabaseIdentifier, Map<ActivityIdentifier, Lock>> locks;
如此含糊地使用:
synchronized (locks.get(databaseIdentifier).get(activityIdentifier)) {
performSpecificActivityOnDatabase();
}
如果您预先知道所有数据库和活动是什么,那么只需在您的应用程序启动时创建一个包含所有组合的完美法线贴图,并完全按照上述方式使用它。唯一的锁定是在锁定对象上,没有争用。
如果您不知道数据库和活动将是什么,或者有太多组合无法预先创建完整的地图,那么您将需要逐步创建地图。这就是并发乐趣时代的开始。
直接的解决方案是懒惰地创建内部映射和锁,并用普通锁保护这些动作:
Map<ActivityIdentifier, Object> locksForDatabase;
synchronized (locks) {
locksForDatabase = locks.get(databaseIdentifier);
if (locksForDatabase == null) {
locksForDatabase = new HashMap<ActivityIdentifier, Object>();
locks.put(databaseIdentifier, locksForDatabase);
}
}
Object lock;
synchronized (locksForDatabase) {
lock = locksForDatabase.get(locksForDatabase);
if (lock == null) {
lock = new Object();
locksForDatabase.put(locksForDatabase, lock);
}
}
synchronized (lock) {
performSpecificActivityOnDatabase();
}
您显然知道,这将导致过多的争用。我提到它只是为了教学完整性。
您可以通过使外部映射并发来改进它:
ConcurrentMap<DatabaseIdentifier, Map<ActivityIdentifier, Object>> locks;
还有:
Map<ActivityIdentifier, Object> newHashMap = new HashMap<ActivityIdentifier, Object>();
Map<ActivityIdentifier, Object> locksForDatabase = locks.putIfAbsent(databaseIdentifier, newHashMap);
if (locksForDatabase == null) locksForDatabase = newHashMap;
Object lock;
synchronized (locksForDatabase) {
lock = locksForDatabase.get(locksForDatabase);
if (lock == null) {
lock = new Object();
locksForDatabase.put(locksForDatabase, lock);
}
}
synchronized (lock) {
performSpecificActivityOnDatabase();
}
在 put 和 get 期间,您唯一的锁争用将出现在每个数据库映射上,根据您的报告,不会有太多这样的争用。您可以将内部地图转换为 ConcurrentMap 以避免这种情况,但这听起来有点矫枉过正。
然而,将会有源源不断的HashMap 实例被创建并提供给putIfAbsent,然后被丢弃。你可以通过双重检查锁定的后现代原子混音来避免这种情况。将前三行替换为:
Map<ActivityIdentifier, Object> locksForDatabase = locks.get(databaseIdentifier);
if (locksForDatabase == null) {
Map<ActivityIdentifier, Object> newHashMap = new HashMap<ActivityIdentifier, Object>();
locksForDatabase = locks.putIfAbsent(databaseIdentifier, newHashMap);
if (locksForDatabase == null) locksForDatabase = newHashMap;
}
在每个数据库映射已经存在的常见情况下,这将执行单个并发get。在不常见的情况下,它将执行额外但必要的new HashMap() 和putIfAbsent。在极少数情况下它不会,但另一个线程也发现,其中一个线程将执行冗余的new HashMap() 和putIfAbsent。那应该不贵。
实际上,在我看来,这是一个糟糕的主意,您应该将两个标识符粘在一起以形成一个双倍大小的键,并使用它在单个 ConcurrentHashMap 中进行查找。可悲的是,我太懒惰了,无法删除以上内容。将此建议视为阅读本文的特别奖励。
PS 看到一个 Object 的实例只用作锁,我总是有点恼火。我建议打电话给他们LockGuffins。