繁体   English   中英

在 ConcurrentHashMap 中以原子方式 searchKeys() 和 put()

[英]Atomically searchKeys() and put() in a ConcurrentHashMap

我正在用 java 开发一个 web 服务器,除其他外,它应该在几个用户之间实现一个挑战服务。
每个用户一次只能参加一项挑战。
实际上,我将“Challenge”对象存储在ConcurrentHashMap<String, Challenge>并且我使用的是一个String ,它是两个玩家用户名的联合作为映射的键。
例如,如果两个玩家的用户名是“Mickey”和“Goofy”,那么ConcurrentHashMap Challenge对象的键将是字符串:

Mickey:Goofy

ConcurrentHashMap记录两个用户之间的新挑战时,我必须在实际将挑战放入 Map 之前检查他们是否已经参与其他挑战,换句话说,我必须检查 Map 中是否存储了密钥包含想要开始新挑战的玩家的两个用户名之一。

例如,给定一个填充的ConcurrentHashMap<String, Challenge>和用户MickeyGoofy的挑战请求,我想以原子方式知道并且不锁定整个地图,如果他们中的一个(或最终两个)已经是/已经在地图中进行了其他已注册的挑战,如果没有,则将新挑战放入地图中。
我希望已经足够清楚了。

你们有什么建议吗?

提前致谢。

对于复合键,使用字符串连接是一个糟糕的选择。 字符串连接是一项开销很大的操作,它不能保证唯一性,因为当其中一个字符串包含您选择的分隔符时,键就会变得不明确。

当然,您可以禁止用户名中的特定字符,但这会增加您必须检查的额外要求,而拥有两个引用的专用键对象更简单、更有效。 您甚至可以使用两个元素List<String>作为附加键类型,因为它具有有用的hashCodeequals实现。

但是由于无论如何您都想对复合键的两个部分执行查找,因此您首先不应该使用复合键。 只需将两个用户名与相同的Challenge对象相关联。 这仍然无法在单个原子操作中完成,但它不需要:

final ConcurrentHashMap<String, Challenge> challenges = new ConcurrentHashMap<>();

Challenge startNewChallenge(String user1, String user2) {
    if(user1.equals(user2))
        throw new IllegalArgumentException("same user");

    Challenge c = new Challenge();

    if(challenges.putIfAbsent(user1, c) != null)
        throw new IllegalStateException(user1+" has an ongoing challenge");

    if(challenges.putIfAbsent(user2, c) != null) {
        challenges.remove(user1, c);
        throw new IllegalStateException(user2+" has an ongoing challenge");
    }

    return c;
}

此代码永远不会覆盖现有值。 如果两个putIfAbsent都成功,那么这两个用户肯定没有正在进行的挑战,现在都与同一个新挑战相关联。

当第一个putIfAbsent成功但第二个失败时,我们必须删除第一个关联。 remove(user1, c)只会在用户仍然与我们的新挑战相关联时将其删除。 当地图上的所有操作都遵循永不覆盖现有条目的原则时(除非满足所有先决条件),这不是必需的,普通的remove(user1)也可以。 但是在这里使用安全变体并没有什么坏处。

非原子性的唯一问题是,由于临时添加的第一个用户,涉及同一用户的两次重叠尝试可能都失败,而实际上其中一个可能会成功。 我不认为这是一个重大问题; 用户根本不应该尝试同时加入两个挑战。

您必须检查您的代码。

您不能一次完成此操作,因为您有两个名称要检查。 即使以传统(迭代)方式,在最好的情况下您也会有两个操作。 所以无论如何,您至少需要在地图上进行两次访问。 我建议您使用不连接字符串的实际地图,所以是的,一个挑战将在地图中出现两次,每个参与者一个。 然后您将能够轻松检查用户是否参与。

如果您需要知道他与谁订婚,只需将两个名字存储在 Challenge 类中。

当然,当您查找这两个条目时,请锁定您的地图。 返回布尔值的函数将完成这项工作!

从我的角度来看这是可能的,但是地图必须使用单个玩家的名字作为键,所以对于两个玩家,我们必须两次挑战一次。 有了这个,我们可以引入额外的异步检查新挑战是否成功为两个玩家存储。

private boolean put(Map<String, Challenge> challenges, String firstPlayerName,
        String secondPlayerName,
        Challenge newChallenge) {

    if(firstPlayerName.compareTo(secondPlayerName) > 0) {
        String tmp = firstPlayerName;
        firstPlayerName = secondPlayerName;
        secondPlayerName = tmp;
    }

    boolean firstPlayerAccepted = newChallenge == challenges.merge(firstPlayerName, newChallenge,
            (oldValue, newValue) -> oldValue.isInitiated() ? oldValue : newValue);
    boolean secondPlayerAccepted = firstPlayerAccepted
            && newChallenge == challenges.merge(secondPlayerName, newChallenge,
            (oldValue, newValue) -> oldValue.isInitiated() ? oldValue : newValue);

    boolean success = firstPlayerAccepted && secondPlayerAccepted;
    newChallenge.initiate(success);
    if (firstPlayerAccepted) {
        // remove unsuccessful
        challenges.computeIfPresent(firstPlayerName, (s, challenge) -> challenge.isInitiated() ? challenge : null);
        if (secondPlayerAccepted) {
            challenges.computeIfPresent(secondPlayerName, (s, challenge) -> challenge.isInitiated() ? challenge : null);
        }
    }
    return success;
}
class Challenge {

    private final CompletableFuture<Boolean> initiated = new CompletableFuture<>();

    public void initiate(boolean success) {
        initiated.complete(success);
    }

    public boolean isInitiated() {
        try {
            return initiated.get();
        } catch (ExecutionException e) {
            throw new IllegalStateException(e);
        } catch (InterruptedException e) {
            return false;
        }
    }
    enter code here
...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM