Java 如何在Map中处理对列表的同步访问?
更新:请注意。 我提出的问题得到了回答。不幸的是,这个问题比标题中的问题要大得多。除了向地图添加新条目外,我还必须同时处理更新和删除。我心目中的情景似乎不可能在没有一个或另一个的情况下实现: A.僵局 B复杂且耗时的检查和锁定 检查问题的底部,了解最后的想法 原职: 嗨 我有一个带地图的春豆 以下是我想使用它的目的: 很少有并发JMS侦听器会接收带有操作的消息。每个操作由两个用户组成:long userA和long userB。消息将有自己的字符串replyTo队列,用于标识操作。 因为当一个用户参与另一个正在执行的操作时,我不能允许执行一个操作,所以我将使用此映射作为正在进行的操作的注册表,以控制操作的执行。 假设我收到三个动作: 1.用户A,用户B 2.用户B,用户C 3.userC,userA 当收到第一个动作时,映射是空的,所以我将在其中记录关于动作的信息,并开始执行动作。 当收到第二个动作时,我可以看到userB正忙于第一个动作,所以我只需记录有关该动作的信息。 第三个动作也是一样 地图将如下所示: [userA:[action1,action3], 用户B:[action1,action2], 用户C:[操作2,操作3]] 第一个操作完成后,我将从注册表中删除有关它的信息,并获取有关userA和userB下一个操作的信息[action3,action2]。然后我将尝试重新启动它们 我想现在你已经明白我想用这张地图做什么了 因为map将同时从多个线程访问,所以我必须以某种方式处理同步 我将有方法向地图添加新信息,并在操作完成时从地图中删除信息。remove方法将为刚刚完成操作的两个用户返回下一个操作[如果有] 因为可能会同时执行数百个操作,而且繁忙用户的操作百分比应该很低,所以我不想阻止每次添加/删除操作对映射的访问 我考虑只对映射中的每个列表进行同步访问,以允许同时访问多个用户条目。然而因为当用户没有操作时,我想从映射中删除该用户的条目。而且当用户在地图中没有条目时,我必须创建一个条目。我有点担心那里可能会有冲突 处理这种情况的最佳方法是什么? 使两种方法——加法和移除-同步,我认为最坏的情况是唯一合适的[安全]方法吗? 此外,我将有另一个映射,其中包含作为键的操作id和作为值的用户id,以便更容易识别/删除用户对。我相信我可以跳过这一步的同步,因为不存在一个动作同时执行两次的场景 虽然Groovy中有代码,但我相信没有Java程序员会发现它很难阅读。它背后是Java。 请考虑下面的伪代码,因为我只是原型。Java 如何在Map中处理对列表的同步访问?,java,collections,concurrency,groovy,synchronization,Java,Collections,Concurrency,Groovy,Synchronization,更新:请注意。 我提出的问题得到了回答。不幸的是,这个问题比标题中的问题要大得多。除了向地图添加新条目外,我还必须同时处理更新和删除。我心目中的情景似乎不可能在没有一个或另一个的情况下实现: A.僵局 B复杂且耗时的检查和锁定 检查问题的底部,了解最后的想法 原职: 嗨 我有一个带地图的春豆 以下是我想使用它的目的: 很少有并发JMS侦听器会接收带有操作的消息。每个操作由两个用户组成:long userA和long userB。消息将有自己的字符串replyTo队列,用于标识操作。 因为当一个用
class UserRegistry {
// ['actionA':[userA, userB]]
// ['actionB':[userC, userA]]
// ['actionC':[userB, userC]]
private Map<String, List<Long>> messages = [:]
/**
* ['userA':['actionA', 'actionB'],
* ['userB':['actionA', 'actionC'],
* ['userC':['actionB', 'actionC']
*/
private Map<long, List<String>> users = [:].asSynchronized()
/**
* Function will add entries for users and action to the registry.
* @param userA
* @param userB
* @param action
* @return true if a new entry was added, false if entries for at least one user already existed
*/
public boolean add(long userA, long userB, String action) {
boolean userABusy = users.containsKey(userA)
boolean userBBusy = users.containsKey(userB)
boolean retValue
if (userABusy || userBBusy) {
if (userABusy) {
users.get(userA).add(action)
} else {
users.put(userA, [action].asSynchronized())
}
if (userBBusy) {
users.get(userB).add(action)
} else {
users.put(userB, [action].asSynchronized())
}
messages.put(action, [userA, userB])
retValue = false
} else {
users.put(userA, [action].asSynchronized())
users.put(userB, [action].asSynchronized())
messages.put(action, [userA, userB])
retValue = true
}
return retValue
}
public List remove(String action) {
if(!messages.containsKey(action)) throw new Exception("we're screwed, I'll figure this out later")
List nextActions = []
long userA = messages.get(action).get(0)
long userB = messages.get(action).get(1)
if (users.get(userA).size() > 1) {
users.get(userA).remove(0)
nextActions.add(users.get(userA).get(0))
} else {
users.remove(userA)
}
if (users.get(userB).size() > 1) {
users.get(userB).remove(0)
nextActions.add(users.get(userB).get(0))
} else {
users.remove(userB)
}
messages.remove(action)
return nextActions
}
}
这样,当操作执行完成时,我使用以下方法从映射中删除条目:
userAId、userBId、actionId
并获取关于userA和userB上的第一个非阻塞等待操作的详细信息(如果有),并将它们传递给执行
现在我需要两种方法,将数据写入映射并将其从映射中删除
public boolean add(long userA, long userB, String action) {
boolean userAEntryExists = users.containsKey(userA)
boolean userBEntryExists = users.containsKey(userB)
boolean actionWaiting = true
UserRegistryEntry userAEntry = new UserRegistryEntry(actionId: action, waiting: false)
UserRegistryEntry userBEntry = new UserRegistryEntry(actionId: action, waiting: false)
if (userAEntryExists || userBEntryExists) {
if (userAEntryExists) {
for (entry in users.get(userA)) {
if (!entry.waiting) {
userAEntry.waiting = true
userBEntry.waiting = true
actionWaiting = true
break;
}
}
}
if (!actionWaiting && userBEntryExists) {
for (entry in users.get(userB)) {
if (!entry.waiting) {
userAEntry.waiting = true
userBEntry.waiting = true
actionWaiting = true
break;
}
}
}
}
if (userBEntryExists) {
users.get(userA).add(userAEntry)
} else {
users.put(userA, [userAEntry])
}
if (userAEntryExists) {
users.get(userB).add(userBEntry)
} else {
users.put(userB, [userBEntry])
}
return actionWaiting
}
至于:
public List remove(long userA, long userB, String action) {
List<String> nextActions = []
finishActionAndReturnNew(userA, action, nextActions)
finishActionAndReturnNew(userB, action, nextActions)
return nextActions;
}
private def finishActionAndReturnNew(long userA, String action, List<String> nextActions) {
boolean userRemoved = false
boolean actionFound = false
Iterator itA = users.get(userA).iterator()
while (itA.hasNext()) {
UserRegistryEntry entry = itA.next()
if (!userRemoved && entry.actionId == action) {
itA.remove()
} else {
if (!actionFound && isUserFree(entry.otherUser)) {
nextActions.add(entry.actionId)
}
}
if (userRemoved && actionFound) break
}
}
public boolean isUserFree(long userId) {
boolean userFree = true
if (!users.containsKey(userId)) return true
for (entry in users.get(userId)) {
if (!entry.waiting) userFree = false
}
return userFree
}
最后的想法:
这种情况是致命的:
[ActionID、userA、userB]
[a,1,2]
[b,1,3]
[c,3,4]
[d,3,1]
动作a和c同时执行,b和d正在等待。
完成a和c后,必须删除用户1、2、3、4的条目,因此一个线程将锁定1和2,另一个线程将锁定3和4。当这些用户被锁定时,必须对每个用户执行下一步操作的检查。当代码确定对于用户1,下一个操作是针对用户3,对于用户3,下一个操作是针对用户1时,whey将尝试锁定它们。这就是死锁发生的时候。我知道我可以编写代码来解决这个问题,但它似乎需要很多时间来执行,并且会阻止两名工人。
现在我将问另一个问题,更多关于我的问题,同时尝试使用JMS来原型化解决方案。您可能想签出或您可能想签出或您在类中有两个全局状态持有者,在修改这两个方法的两个方法中都有复合操作。因此,即使我们将映射更改为ConcurrentHashMap,并将列表更改为类似CopyOnWriteArrayList的内容,它也会 仍然无法保证状态的一致性 我知道你会经常给列表写信,所以CopyOnWriteArrayList可能太贵了。ConcurrentHashMap只有16路分条。如果您有更好的硬件,另一种选择是在方法中适当锁定后使用Cliff Click的highscalelib 回到一致性问题,使用ReentrantLock而不是同步,看看是否可以从锁中排除一些语句来解锁序列。如果使用ConcurrentMap,add中的前两条语句do containsKey可能是乐观的,您可以将它们从锁块中排除
你真的需要信息地图吗?它有点像用户的反向索引。另一种选择是使用另一种监视方法,该方法在用户发生更改后根据add发出的信号定期更新消息映射。刷新也可以是完全异步的。这样,您就可以在更新消息时对用户使用带有readLock的ReadWriteLock。在这种情况下,add可以安全地获取用户的writeLock。要使这一点合理地正确,还需要做更多的工作。类中有两个全局状态持有者,两个方法中的每一个都有复合动作来修改它们。因此,即使我们将映射更改为ConcurrentHashMap,并将列表更改为类似CopyOnWriteArrayList的内容,也不能保证状态一致 我知道你会经常给列表写信,所以CopyOnWriteArrayList可能太贵了。ConcurrentHashMap只有16路分条。如果您有更好的硬件,另一种选择是在方法中适当锁定后使用Cliff Click的highscalelib 回到一致性问题,使用ReentrantLock而不是同步,看看是否可以从锁中排除一些语句来解锁序列。如果使用ConcurrentMap,add中的前两条语句do containsKey可能是乐观的,您可以将它们从锁块中排除
你真的需要信息地图吗?它有点像用户的反向索引。另一种选择是使用另一种监视方法,该方法在用户发生更改后根据add发出的信号定期更新消息映射。刷新也可以是完全异步的。这样,您就可以在更新消息时对用户使用带有readLock的ReadWriteLock。在这种情况下,add可以安全地获取用户的writeLock。要使这一点合理正确,还需要做一些工作。您可能需要再次查看同步收集的工作方式: 这是一个非独占示例,它不是线程安全的:
if (users.get(userA).size() > 1) {
users.get(userA).remove(0)
请记住,只有个别的同步方法是保证原子的,没有更大的锁定范围
快乐编码
编辑-已更新每个用户的同步锁以供评论:
仅通过使用标准数据结构,您就可以通过使用实现每键锁,特别是通过使用“putIfAbsent”方法。这与仅使用“同步HashMap”的get/put有很大不同,见上文
下面是一些伪代码和注释:
public boolean add(long userA, long userB, String action) {
// The put-if-absent ensures the *the same* object but may be violated when:
// -users is re-assigned
// -following approach is violated
// A new list is created if needed and the current list is returned if
// it already exists (as per the method name).
// Since we have synchronized manually here, these lists
// themselves do not need to be synchronized, provided:
// Access should consistently be protected across the "higher"
// structure (per user-entry in the map) when using this approach.
List listA = users.putIfAbsent(userA, new List)
List listB = users.putIfAbsent(userB, new List)
// The locks must be ordered consistently so that
// a A B/B A deadlock does not occur.
Object lock1, lock2
if (userA < userB) {
lock1 = listA, lock2 = listB
} else {
lock1 = listB, lock2 = listA
}
synchronized (lock1) { synchronized (lock2) {{ // start locks
// The rest of the code can be simplified, since the
// list items are already *guaranteed* to exist there is no
// need to alternate between add and creating a new list.
bool eitherUserBusy = listA.length > 0 || listB.length > 0
listA.add(action)
listB.add(action)
// make sure messages allows thread-safe access as well
messages.put(action, [userA, userB])
return !eitherUserBusy
}} // end locks
}
我不知道在您的使用场景下,与单个公共锁对象相比,这是如何公平的。通常建议使用simpler,除非有明显的优势
HTH和Happy编码。您可能需要再次查看同步收集的工作方式: 这是一个非独占示例,它不是线程安全的:
if (users.get(userA).size() > 1) {
users.get(userA).remove(0)
请记住,只有个别的同步方法是保证原子的,没有更大的锁定范围
快乐编码
编辑-已更新每个用户的同步锁以供评论:
仅通过使用标准数据结构,您就可以通过使用实现每键锁,特别是通过使用“putIfAbsent”方法。这与仅使用“同步HashMap”的get/put有很大不同,见上文
下面是一些伪代码和注释:
public boolean add(long userA, long userB, String action) {
// The put-if-absent ensures the *the same* object but may be violated when:
// -users is re-assigned
// -following approach is violated
// A new list is created if needed and the current list is returned if
// it already exists (as per the method name).
// Since we have synchronized manually here, these lists
// themselves do not need to be synchronized, provided:
// Access should consistently be protected across the "higher"
// structure (per user-entry in the map) when using this approach.
List listA = users.putIfAbsent(userA, new List)
List listB = users.putIfAbsent(userB, new List)
// The locks must be ordered consistently so that
// a A B/B A deadlock does not occur.
Object lock1, lock2
if (userA < userB) {
lock1 = listA, lock2 = listB
} else {
lock1 = listB, lock2 = listA
}
synchronized (lock1) { synchronized (lock2) {{ // start locks
// The rest of the code can be simplified, since the
// list items are already *guaranteed* to exist there is no
// need to alternate between add and creating a new list.
bool eitherUserBusy = listA.length > 0 || listB.length > 0
listA.add(action)
listB.add(action)
// make sure messages allows thread-safe access as well
messages.put(action, [userA, userB])
return !eitherUserBusy
}} // end locks
}
我不知道在您的使用场景下,与单个公共锁对象相比,这是如何公平的。通常建议使用simpler,除非有明显的优势
HTH和Happy编码。原语不能是泛型类型。用长而不是长。谢谢雷欧,正如我在这个问题中所写的,认为这是一个伪代码。我只是想把我想用地图做的事情形象化。原语不能是泛型的。用长而不是长。谢谢雷欧,正如我在这个问题中所写的,认为这是一个伪代码。我只是想或多或少地想象一下我想对地图做些什么。[:]。asSynchronized表示集合。SynchronizedMasnewHashMap;但我不确定我在这里做的是否明智。从性能和数据完整性的角度来看,这两个方面都不能解决上述问题。特别是,没有一个包含同步映射的显式锁,同步映射是完美的
如果可行,则无法获取原子测试并设置语义或类似。[::]asSynchronized表示集合。SynchronizedMasnew HashMap;但我不确定我在这里做的是否明智。从性能和数据完整性的角度来看,这两个方面都不能解决上述问题。特别是,如果没有包含完全可行的同步映射的显式锁,就无法获得原子测试并设置语义或类似的。我昨晚考虑过了,是的,消息地图是不需要的,但这将意味着另一个地图的外观有所改变。看一下编辑。仍然不确定我需要做什么才能使它在不阻塞其他线程的情况下工作。谢谢gshx。我昨晚考虑过了,是的,消息地图是不需要的,但这将意味着另一个地图的外观有所改变。看一下编辑。仍然不确定我需要做什么才能使它在不阻塞其他线程的情况下工作。谢谢pst。我要做的是执行一些同步的动作,有些不同步。我想知道是否有可能使地图中的插入同步,以便在创建新密钥时,其他线程无法创建新密钥。然后,如果一个线程修改了映射中键a下的列表,我不希望其他线程访问相同的列表,而是允许访问其他键下的列表。例如,如果我检查map中的一个键是否存在于一个方法调用中,并且如果它没有单独的同步方法来重新检查并在需要时添加条目,那么它会工作吗?这样行吗?@Krystian更新后的答案可能会回答你的一些问题。我想您会发现putIfAbsent方法是一种合适的方法。我试图在两个级别上实现ReentrantReadWriteLock:用户映射和映射条目中的每个列表。这似乎是一个更好的方法。我将尝试使用适当的remove命令来实现它,看看它是如何运行的。在remove命令中,我必须同时检查并锁定映射内的多个列表[如果存在],因为我必须与刚完成执行的用户一起检查下一步操作的用户可用性。[在我上次编辑的问题中描述了该场景]我阅读了有关ConcurrentHashMap的文章,虽然它非常适合插入新条目,但在删除过程中操作起来可能不太好。在删除过程中,我必须更新两个条目,锁定条目,检查[我的意思是锁定]其他条目,如果需要,更新它们。在这种情况下,我认为这意味着一个内部有一个同步块的循环。听起来很奇怪。今天晚些时候我将尝试制作原型。再次感谢!你好。我在putIfAbsent和synchronized块之间获得NPE,因为在我进入synchronized之前,其他线程删除了映射条目。另外,我在不同的删除调用之间遇到死锁。我开始认为不锁定地图是不可能的。谢谢pst。我要做的是执行一些同步的动作,有些不同步。我想知道是否有可能使地图中的插入同步,以便在创建新密钥时,其他线程无法创建新密钥。然后,如果一个线程修改了映射中键a下的列表,我不希望其他线程访问相同的列表,而是允许访问其他键下的列表。例如,如果我检查map中的一个键是否存在于一个方法调用中,并且如果它没有单独的同步方法来重新检查并在需要时添加条目,那么它会工作吗?这样行吗?@Krystian更新后的答案可能会回答你的一些问题。我想您会发现putIfAbsent方法是一种合适的方法。我试图在两个级别上实现ReentrantReadWriteLock:用户映射和映射条目中的每个列表。这似乎是一个更好的方法。我将尝试使用适当的remove命令来实现它,看看它是如何运行的。在remove命令中,我必须同时检查并锁定映射内的多个列表[如果存在],因为我必须与刚完成执行的用户一起检查下一步操作的用户可用性。[在我上次编辑的问题中描述了该场景]我阅读了有关ConcurrentHashMap的文章,虽然它非常适合插入新条目,但在删除过程中操作起来可能不太好。在删除过程中,我必须更新两个条目,锁定条目,检查[我的意思是锁定]其他条目,如果需要,更新它们。在这种情况下,我认为这意味着一个内部有一个同步块的循环。听起来很奇怪。今天晚些时候我将尝试制作原型。再次感谢!你好。我在putIfAbsent和synchronized块之间获得NPE,因为在我进入synchronized之前,其他线程删除了映射条目。另外,我在不同的删除调用之间遇到死锁。我开始认为不锁定地图是不可能的。