Подтвердить что ты не робот

Нужно ли сделать ConcurrentHashMap изменчивым?

У нас есть общий ConcurrentHashMap, который читается и записывается двумя потоками.

class Test {

    ConcurrentHashMap map;

    read() {
        map.get(object);
    }

    write() {
        map.put(key, object);
    }
}

Нужно ли сделать карту изменчивой, чтобы записи одного потока видны потоками чтения как можно скорее?

Возможно ли, что пометка на карту в одном потоке не видна или замечена очень поздно другим телом? Тот же вопрос для HashMap.

4b9b3361

Ответ 1

Если вы можете сделать это final, тогда сделайте это. Если вы не можете сделать это final, тогда да, вам нужно будет сделать это volatile. volatile применяется к назначению поля, и если это не final, тогда есть возможность (по крайней мере, для JMM), чтобы запись поля CHM одним потоком не могла быть видима для другого потока. Чтобы повторить, это назначение поля ConcurrentHashMap и не использование CHM.

При этом вы должны действительно сделать это final.

Нужно ли сделать карту изменчивой, чтобы записи одного потока видны потоками чтения как можно скорее?

Если записи, о которых вы говорите, выполняются с использованием методов мутации самого CHM (например, put или remove), вы делаете поле volatile недействительным. Все гарантии видимости памяти выполняются в CHM.

Возможно ли, что пометка на карту в одном потоке не видна или замечена очень поздно другим телом? Тот же вопрос для HashMap.

Не для ConcurrentHashMap. Если вы используете простой HashMap одновременно, не делайте этого. См.: http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html

Ответ 2

volatile применяется случается перед семантикой при чтении и записи соответствующей переменной.

Поле может быть объявлено volatile, и в этом случае модель памяти Java гарантирует, что все потоки будут видеть согласованное значение для переменной (§17.4).

Он не имеет ничего общего с объектами, на которые ссылается значение переменной. Итак, если вы не изменяете переменную в нескольких потоках, нет, вам не нужно делать ее volatile.

Как указано javadoc, все методы ConcurrentHashMap действуют как барьеры памяти,

Retrievals отражают результаты последнего завершенного обновления операции с их началом. (Более формально, обновление операция для заданного ключа несет связь с прошлым (non-null) для этого ключа, сообщающего обновленное значение.

Ответ 3

Здесь есть два вопроса: видимость ссылки на карту и видимость значений, записанных на карту.

  • Видимость ссылки на карту:

    Нужно ли делать карту...

Вы должны заботиться о безопасной публикации своих ссылок в многопоточной среде. Безопасная публикация означает, что все значения, написанные до публикации, видны всем читателям, которые наблюдают за опубликованным объектом. Существует несколько способов опубликования ссылки в соответствии с JMM:
  • Предоставить доступ к ссылке через правильно заблокированное поле (JLS 17.4.5)
  • Используйте статический инициализатор для создания хранилищ инициализации (JLS 12.4) (не наш случай на самом деле)
  • Предоставить доступ к ссылке через поле volatile (JLS 17.4.5) или как следствие этого правила через классы AtomicX, такие как AtomicReference
  • Инициализировать значение как окончательное поле (JLS 17.5)

Итак, в вашем случае ссылка на вашу "карту" опубликована неправильно. Это может вызвать NullPointerException в Test.read() или/и Test.write() (это зависит от того, какой поток создает ConcurrentHashMap и помещает его в поле "map" ). Правильный код будет одним из следующих:

//1. Provide access to the reference through a properly locked field
class Test {

    ConcurrentHashMap map;

    synchronized void init(ConcurrentHashMap map) {
        this.map = map;
    }

    synchronized void read() {
        map.get(object);
    }

    synchronized void write() {
        map.put(key, object);
    }
}

// or
class Test {
    ReadWriteLock rwl = new ReentrantReadWriteLock();

    ConcurrentHashMap map;

    void init(ConcurrentHashMap map) {
        rwl.writeLock().lock();
        this.map = map;
        rwl.writeLock().release();
    }

    void read() {
        rwl.readLock().lock();
        try {
            map.get(object);
        } finally {
          rwl.readLock().release();
        }
    }

    void write() {
        rwl.writeLock().lock();
        try {
            map.put(key, object);
        } finally {
            rwl.writeLock().release();
        }
    }
}

// 3. Provide access to the reference via a volatile field
class Test {

    volatile ConcurrentHashMap map; // or AtomicReference<ConcurrentHashMap> map = new AtomicReference();

    void init(ConcurrentHashMap map) {
        this.map = map;
    }

    void read() {
        map.get(object);
    }

    void write() {
        map.put(key, object);
    }
}

// 4. Initialize the value as a final field
class Test {

    final ConcurrentHashMap map;

    Test(ConcurrentHashMap map) {
        this.map = map;
    }

    void read() {
        map.get(object);
    }

    void write() {
        map.put(key, object);
    }
}

Конечно, вы можете использовать простой HashMap в случае с .1 (когда вы работаете с правильно заблокированным полем "карта" ) вместо ConcurrentHashMap. Но если вы все еще хотите использовать ConcurrentHashMap для лучшей производительности, лучший способ опубликовать свою "карту" правильно, как видите, сделать поле окончательным.

Вот хорошая статья о безопасной публикации от парня Oracle, кстати: http://shipilev.net/blog/2014/safe-public-construction/

  1. Видимость значений, записанных на карту:

Возможно ли, что на карту в один поток не видно или не видно очень поздно другой темой?

Нет, если вы не получаете NPE (см. стр. 1) или правильно опубликовали свою карту, читатель всегда видит все изменения, создаваемые автором, поскольку пара ConcurrentHashMap.put/get создает соответствующие барьеры памяти /Happens -Before edge.

Тот же вопрос для HashMap

HashMap вообще не является потокобезопасным. Методы HashMap.put/получить работу с внутренним состоянием карты небезопасным потоком (неатомный, без видимости на нескольких потоках измененного состояния гарантированно), поэтому вы можете просто испортить состояние карты. Это означает, что должен использовать соответствующий механизм блокировки (синхронизированные разделы, ReadWriteLock и т.д.) Для работы с HashMap. И в результате блокировки вы достигаете того, что вам нужно - читатель всегда видит все изменения, создаваемые автором, потому что эти блокировки создают барьеры памяти /Happens -Before edge.

Ответ 4

Нет, вы этого не делаете.

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

В этом случае переменная является ссылкой на карту. Вы используете одну и ту же карту все время, поэтому вы не меняете ссылку - скорее вы меняете содержимое этой Карты. (Это означает, что Карта изменена.) Это также означает, что вы можете и поэтому должны сделать ссылку на карту final.

ConcurrentHashMap отличается от HashMap тем, что вы, как правило, безопасно читаете его и записываете в него одновременно из разных потоков без внешней блокировки. Если вы, однако, хотите иметь возможность доверять размеру в любой заданной точке, выполнять операции "check-then-write" или т.п., Вам нужно создать это самостоятельно.