【问题标题】:Simultaneous read write on value object of ConcurrentHashMapConcurrentHashMap的值对象同时读写
【发布时间】:2018-11-03 15:40:29
【问题描述】:

我有一个 ConcurrentHashMap:

ConcurrentHashMap<ID,Object> map;

在我的应用中,这张图是高读低写的

通过以下方式阅读作品:

public Response getObject() {
    Response response = createResponse();
    Object obj = map.get(ID);
    if (obj != null) {
        if (obj.getAttribute1() == some_value) {
            response.setAttr1(obj.getAttr1());
            response.setAttr2(obj.getAttr2());
        }
    }
    return response;
}

更新的工作方式如下:

public void updateObject(Object obj, int action) {
    if (action == ADD) {
        map.put(obj.getID(), obj);
    } else if (action == UPDATE) {
        object oldObj = map.get(obj.getID());
        if (oldObj != null) {
            map.put(obj.getID(), obj);
        }
    } else if (action == REMOVE) {
        object oldObj = map.get(obj.getID());
        if (oldObj != null) {
            map.remove(obj.getID());
        }
    }
}

现在我的问题是,ConcurrentHashMap 是否足以让上述情况在多线程环境中以线程安全的方式工作,还是我必须通过读写锁从外部锁定对象或使用对象的克隆?

假设在从 map 读取 obj 的情况下,ConcurrentHashMap 将确保它会返回最新写入的对象,但是当写入线程在读取之后删除/更新该对象时呢?读取对象(已被删除/更新from map) 用于准备响应对象,其属性用于做出某些决定。

更新地图的更好方法应该是什么?

【问题讨论】:

    标签: java multithreading concurrenthashmap


    【解决方案1】:

    为了使您的代码相对于ConcurrentHashMap 是线程安全的,您应该使用各自的compute 方法(以原子方式执行):

    public Response getObject() {
        Response response = createResponse();
    
        map.computeIfPresent(ID, (k, v) -> {
            if (v == some_value) {
                response.setAttr1(v.getAttr1());
                response.setAttr2(v.getAttr2());
            }
    
            return v;
        }
    
        return response;
    }
    

    还有:

    public void updateObject(Object obj, int action) {
        if (action == ADD) {
            map.put(obj.getID(), obj);
        } else if (action == UPDATE) {
            map.computeIfPresent(obj.getID(), (k, v) -> obj);
        } else if (action == REMOVE) {
            map.remove(obj.getID());
        }
    }
    

    【讨论】:

    • 除了不需要检查是否存在之外,读取不会通过使用计算获得任何收益。
    【解决方案2】:

    如果只有一个写入器,这是线程安全的。否则,您执行的操作之间存在竞争条件。出于这个原因,有些操作可以原子地执行这些操作并简化代码。

    public void updateObject(Object obj, int action) {
        switch (action) {
            case ADD:
                map.put(obj.getID(), obj);
                break;
    
            case UPDATE:
                map.computeIfPresent(obj.getID(), (k, v) -> obj);
                break;
    
            case REMOVE:
                map.remove(obj.getID());
                break;
        }
    }
    

    很可能,您不需要特殊的更新操作,您也可以使用 put

    我强烈建议不要使用 Object 作为自定义类,而是使用新名称。

    当这个对象在读取后被写入线程删除/更新时会怎样

    如果对象被删除,它不会影响另一个持有该引用的线程。如果通过在地图中放置新对象来更新对象,这会很好,但是如果您更新添加的对象,这可能不是线程安全的。

    【讨论】:

    • 我没有投反对票,但我怀疑这是因为Map#putIfPresent 不存在。
    猜你喜欢
    • 1970-01-01
    • 2020-08-17
    • 2013-08-31
    • 2017-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多