Одновременное чтение, запись по значению объекта ConcurrentHashMap - PullRequest
0 голосов
/ 03 ноября 2018

У меня есть 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 достаточным для вышеприведенного случая, чтобы работать в многопоточном режиме безопасным для потоков образом, или мне приходится внешне блокировать объект с помощью блокировки ReadWrite или использовать клонирование объекта?

Предположим, что в случае, когда объект obj читается из карты, ConcurrentHashMap удостоверится, что он вернет последний записанный объект, но что делать, когда этот объект удаляется / обновляется потоком записи сразу после чтения. Объект чтения (который уже удален / обновлен) из карты) используется для подготовки объекта ответа, а его атрибут - для принятия определенных решений.

А каким должен быть лучший способ обновить карту?

Ответы [ 2 ]

0 голосов
/ 03 ноября 2018

Чтобы ваш код был потокобезопасным по отношению к 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());
    }
}
0 голосов
/ 03 ноября 2018

Это потокобезопасно при условии, что есть только один писатель. В противном случае между выполненными вами операциями возникает состояние гонки. По этой причине существуют операции, которые выполняют эти действия атомарно и упрощают код.

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 в качестве пользовательского класса, а вместо этого использовать новое имя.

что делать, когда этот объект удаляется / обновляется потоком писателя сразу после прочтения

Если объект удален, он не влияет на другой поток, содержащий эту ссылку. Если объект обновляется путем помещения нового объекта на карту, это будет хорошо, однако, если вы обновите добавленный объект, это может быть не безопасно для потоков.

...