有条件地在ConcurentHashMap中计算值并在某些时间间隔替换它

时间:2017-06-12 16:02:33

标签: java dictionary concurrency

在我的应用程序中有一个共享缓存服务。它由多个线程访问,因此它必须是线程安全的。我已经使用HashMap实现了它,因为我同步了getOrCreateToken()方法,但我也使用了ConcurentHashMap。我的问题是我不想执行service.authenticateWithoutCache()如果不需要(这是API限制,我有义务只在实际需要时才发出请求)。但是,如果多个线程通过标记 !!! MARKER !!! ,则会发出几个新的NEEDLES标记。 computeIfAbsent将不起作用,因为该标记应该在某些时间间隔重新发布。我想找到一种方法,只有在条件满足时才更换旧票证,而不是每次多线程进入if语句时?

注意:在使用synchronized HashMap的实现中,这很容易实现,因为synchronized块保证只有一个线程正在执行块。

    private static Map<String, TokenResponse> cachedKeys = new ConcurrentHashMap<>();

    public static TokenResponse getOrCreateToken(OAuthService service)
        throws IOException {

    String accessTokenKey = constructAccessTokenKey(configuration);
    TokenResponse token = cachedKeys.get(accessTokenKey);
    if (token == null) {
        TokenResponse alreadyThereToken = cachedKeys.computeIfAbsent(accessTokenKey,
                key -> service.authenticateWithoutCache());
        return alreadyThereToken;
    } else {
        Instant now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
        Instant createdAt = token.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant();

        long minutes = accessTokenExpiredMinutes(createdAt, now);
        //!!!MARKER!!!
        if (minutes > 90) {
            //Here several threads can execute this line
            //And service.authenticateWithoutCache() will be called several times and this should not happen. 
            //If I execute computeIfAbsent it will be evaluated only the 
            //first time a thread reach this code
            // and it won't be updated anymore even if access token expires.
            return cachedKeys.compute(accessTokenKey, (key, value) -> 
                             service.authenticateWithoutCache() 

            });
        }

    }
    return token;
}

1 个答案:

答案 0 :(得分:0)

您必须将令牌生成放入同步块中,尝试在该块中再次获取它并在每个线程中重试它。类似的东西:

TokenResponse getOrCreateFor(String accessTokenKey){
  TokenResponse token = cachedKeys.get(accessTokenKey);
  if (token != null)
    return token;

  synchronized(cachedKeys){
    token = cachedKeys.get(accessTokenKey);
    if (token != null)
      return token;
    return cachedKeys.computeIfAbsent(accessTokenKey,
             key -> service.authenticateWithoutCache());
  }
}

如果您只是在超时发生之前清除令牌并在之后使用该方法一切都应该没问题:-)

这样只有令牌的创建才会同步,其他一切都是并行的。

如果你想为不同的用户提供并行标记,你需要为accessTokenKeys提供类似于HashSet的东西,每个都有自己的每个accesTokenKey对象来锁定authenticateWithoutCache。但访问它也必须是线程安全的,也许有一个很好的非锁定解决方案..但我会延迟,直到需要。