Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/jsf/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何在Map中处理对列表的同步访问?_Java_Collections_Concurrency_Groovy_Synchronization - Fatal编程技术网

Java 如何在Map中处理对列表的同步访问?

Java 如何在Map中处理对列表的同步访问?,java,collections,concurrency,groovy,synchronization,Java,Collections,Concurrency,Groovy,Synchronization,更新:请注意。 我提出的问题得到了回答。不幸的是,这个问题比标题中的问题要大得多。除了向地图添加新条目外,我还必须同时处理更新和删除。我心目中的情景似乎不可能在没有一个或另一个的情况下实现: A.僵局 B复杂且耗时的检查和锁定 检查问题的底部,了解最后的想法 原职: 嗨 我有一个带地图的春豆 以下是我想使用它的目的: 很少有并发JMS侦听器会接收带有操作的消息。每个操作由两个用户组成:long userA和long userB。消息将有自己的字符串replyTo队列,用于标识操作。 因为当一个用

更新:请注意。 我提出的问题得到了回答。不幸的是,这个问题比标题中的问题要大得多。除了向地图添加新条目外,我还必须同时处理更新和删除。我心目中的情景似乎不可能在没有一个或另一个的情况下实现: 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。 请考虑下面的伪代码,因为我只是原型。
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之前,其他线程删除了映射条目。另外,我在不同的删除调用之间遇到死锁。我开始认为不锁定地图是不可能的。