Синхронизация на объекте и изменение ссылки

Допустим, у меня есть объект следующим образом:

Map<String, String> m = new HashMap<>();

Затем я синхронизируюсь с этим объектом следующим образом и меняю его ссылку:

synchronize(m){
    m = new HashMap<>();
}

С этим кодом, что происходит с блокировкой на m? Все еще безопасно обновлять новый объект, представленный m? Или блокировка по существу на старом объекте?

4 ответа

Решение

Из JLS 17.1:

Синхронизированный оператор (§14.19) вычисляет ссылку на объект; затем он пытается выполнить действие блокировки на мониторе этого объекта и не продолжается, пока действие блокировки не будет успешно завершено. После выполнения действия блокировки выполняется тело синхронизированного оператора. Если выполнение тела когда-либо завершается, как обычно, так и внезапно, на этом же мониторе автоматически выполняется действие по разблокировке.

Теперь вопросы.

Что происходит с замком на м?

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

Все еще безопасно обновлять новый объект, представленный m?

Это небезопасно. Запись в m не синхронизируется на одной и той же блокировке.

Или блокировка по существу на старом объекте?

да

Блокировка находится на объекте, а не на переменной.

Когда поток пытается войти в синхронизированный блок, он оценивает выражение в скобках после ключевого слова synchronized, чтобы определить, какой объект получить блокировку.

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

Чтобы взаимное исключение работало, вам нужно, чтобы потоки разделяли одну и ту же блокировку, у вас не должно быть потоков, заменяющих объект блокировки. Хорошая идея - иметь выделенный объект, который вы используете в качестве блокировки, чтобы сделать его окончательным, чтобы убедиться, что ничто его не изменит, например так:

private final Object lock = new Object();

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

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

Для безопасного изменения ссылки на объект вы можете:

  1. использование AtomicReference

    AtomicReference<Map<String, String>>
    
  2. использование synchronized на объекте, который содержит эту карту или лучше на другом объекте блокировки.

    class A {
        private final Object lock = new Object();
        private Map<String, String> m = new HashMap<>();
    
        public void changeMap() {
            synchronized(lock){
                m = new HashMap<>();
            }
        }
    }
    
  3. Хотя бы добавить volatile

    private volatile Map<String, String> m = new HashMap<>();
    

Также смотрите другие ответы на эту тему

  1. Поток обновления ссылки безопасен?
  2. Безопасно ли в Java изменять ссылку на HashMap, считываемую одновременно

Ваш подход небезопасен. Вы должны использовать одну и ту же блокировку среди всех координирующих потоков для защиты какого-либо ресурса (карта m в этом случае), но, как вы интуитивно поняли, это терпит неудачу здесь, потому что объект m постоянно меняется.

Чтобы быть конкретным, как только вы напишите новую ссылку на m внутри критической секции другой поток может войти в критическую секцию (так как они получают блокировку новой Map, а не старый, удерживаемый другим потоком), и доступ к новой частично построенной карте.

Смотрите также безопасную публикацию.

Другие вопросы по тегам