使用ConcurrentHashMap时的并发问题

4

作为技能提升的一部分,我一直在开发一个REST API。我的当前实现在将对象插入ConcurrentHashMap时存在小的并发问题。

我的代码会检查所消耗的JSON是否包含ID。如果没有,我会创建一个新的唯一ID并插入该对象。如果有,则继续检查ID是否已经存在于我的映射中。如果不存在具有该ID的对象,则插入该对象。

当许多并发POST请求正在进行时,检查HashMap是否包含匹配ID和插入对象之间的时间间隔成为了一个问题。如果第一个请求在第二个请求的gcdMap.get(obj.getId()) == nullgcdMap.put(obj.getId(), obj);代码行之间执行,那么具有生成的ID的请求可能会被指定ID的请求覆盖。我一直在使用Thread.Sleep()来重现这个问题。

public static ConcurrentMap<Long, GCDObject> gcdMap = new ConcurrentHashMap<Long, GCDObject>();
@POST

@Consumes(MediaType.APPLICATION_JSON)
public GCDObject create(GCDObject obj) throws GCDRequestException {
    obj.setTimestamp(LocalDateTime.now());
    obj.setResult(GCD.calculate(obj.getX(), obj.getY()));

    if (obj.getId() != null) { // JSON contains ID
        if (gcdMap.get(obj.getId()) == null) { // If map does not contain obj with ID already,
            Thread.sleep(1000);
            gcdMap.put(obj.getId(), obj); // Put obj into map.
            return obj;
        } else { // else map already contains ID,
            throw new GCDRequestException();
        }
    } else { // JSON contains no ID
        obj.setId(buildId()); // Build ID
        gcdMap.put(obj.getId(), obj); // Put into map
        return obj;
    }
}

我看到了使用锁的建议,但是没有能够实现解决这个问题的方式。如果有任何示例、文档或文章可以帮助我找到解决方案,将不胜感激。

编辑:我在下面的评论中拼错了“absent”三次。现在我无法编辑它们,但我注意到了!


1
你正在使用Java 8吗? - shmosel
3
你尝试过使用其他ConcurrentHashMap的方法,比如putIfAbsent和getOrDefault吗?还有,这些并发的POST请求是不同的还是相同的? - Neenad
1
你在 buildId() 方法中采用什么样的 ID 生成方式?你是如何计算 ID 的?是顺序的还是随机的?关于锁,你可以从这个链接中了解到一些信息:http://crunchify.com/what-is-lock-unlock-reentrantlock-trylock-and-how-its-different-from-synchronized-block-in-java/ - Neenad
1
如果在您的buildId()方法中,您使用原始数据类型(如int或long)生成普通整数ID,并且您已经将计数器设置为类级别,则可能会导致问题。请使用AtomicInteger或AtomicLong。 - Neenad
1
使用 get,接着是 put 被称为检查-然后-操作反模式。当然,在这里使用锁将会挫败 ConcurrentHashMap 的整个目的,该类专门设计提供原子更新方法(如 putIfAbsent)以避免锁定。 - Holger
显示剩余3条评论
1个回答

9
使用 putIfAbsent() 条件式插入:
if (gcdMap.putIfAbsent(obj.getId(), obj) == null) { // If map did not contain obj with ID already,
    return obj;
} else { // else map already contained ID,
    throw new GCDRequestException();
}

我再次尝试了putIfAbsent()方法,它做到了我所希望的;我想我可能忘记了第一次先重建我的项目。谢谢! - user4759317

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接