Ява. Как правильно синхронизировать геттеры и сеттеры?

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

class Doggie {
    private String name;
    private int    age;

    public void setName(String name) { this.name = name; }
    public String getName() { return this.name; }
    public void setAge(int age) { this.age = age; }
    public int getAge() { return this.age; }

}

Вопросы:

  • Не return и атомарные операции присваивания в Java?
  • Поскольку свойства могут не обязательно быть взаимосвязанными, не всегда имеет смысл синхронизировать с одной и той же блокировкой. Как организовать блокирующую структуру?
  • Лучше ли идти с встроенной блокировкой или частным шаблоном блокировки объекта?

Ответ 1

  • Не возвращаются ли ядерные операции return и assign в Java?

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

  • Когда чтения и записи находятся в одном и том же потоке, чтение, как ожидается, увидит более раннюю запись.

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

Обратите внимание, что примитивные блокировки/мьютексы - это не единственный способ синхронизации.

  • Поскольку свойства могут не обязательно быть взаимосвязанными, не всегда имеет смысл синхронизировать с одной и той же блокировкой. Как организовать блокирующую структуру?

Имеет смысл использовать несколько блокировок, если (и только если) конфликт блокировки вероятен. В вашем примере конфликт блокировки может быть скорее всего проблемой, если какой-то экземпляр Doggie получает очень высокую скорость операций get и/или set.

  • Лучше ли идти с встроенной блокировкой или частным шаблоном блокировки объекта?

Это зависит. Если ваше приложение будет использовать блокировку объекта Doggie, тогда вы можете получить блокировку или даже непреднамеренную блокировку операций get и set. В этом случае может быть целесообразным использование частного замка. В противном случае частная блокировка является ненужной служебной информацией.

Ответ 3

Операции со ссылками являются атомарными, но не volatile - вы всегда увидите старое значение или новое значение, но нет никакой гарантии, что вы увидите новое значение без какого-либо барьера памяти. Я не могу вспомнить детали того, какие примитивы гарантированно будут атомарными - вероятно, все, кроме долгого и двойного.

Лично я бы использовал один закрытый замок, пока не увидел никаких доказательств того, что это было узким местом. Я бы посоветовал блокировать на "this", поскольку другой код также мог бы блокировать его. Если вы единственный код, который знает о блокировке, сложнее получить помехи. Сказав это, если вызывающие абоненты хотят атомарно изменить более одного свойства, вы можете захотеть открыть блокировку через свойство.

Вам определенно нужен изменяемый тип threadafe? Если бы вы могли избежать этого требования, это упростило бы жизнь.

Ответ 4

  • Они являются атомарными операциями, но представляют собой сценарий, в котором два клиента пытаются получить и установить часть данных одновременно. Нет никаких гарантий относительно того, какой порядок будет называться, что может сильно повлиять на результаты вашей заявки. (Классический пример - денежные транзакции.)
  • Возможно, нет смысла синхронизировать с одной и той же блокировкой - это действительно зависит от вашего приложения. Однако обычно не рекомендуется блокировать весь объект, если он не нужен.
  • Как и с тем, что сказал Джон, начните с одиночного частного замка и идите оттуда в зависимости от результатов.

Ответ 5

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

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