Правильный способ синхронизировать доступ к карте только для чтения в Java - PullRequest
3 голосов
/ 11 ноября 2011

Я пишу аналог класса DatabaseConfiguration, который считывает конфигурацию из базы данных, и мне нужен совет по поводу синхронизации. Например,

public class MyDBConfiguration{
   private Connection cn;
   private String table_name;
   private Map<String, String> key_values = new HashMap<String,String>();
   public MyDBConfiguration (Connection cn, String table_name) {
      this.cn = cn;
      this.table_name = table_name;
      reloadConfig();
   }
   public String getProperty(String key){
       return this.key_values.get(key);
   }
   public void reloadConfig() {
      Map<String, String> tmp_map = new HashMap<String,String> ();
      // read  data from database
      synchronized(this.key_values)
      {
          this.key_values = tmp_map;
      }
   }
}

Итак, у меня есть пара вопросов.
1. При условии, что свойства доступны только для чтения, я должен использовать synchronize в getProperty?
2. Имеет ли смысл делать this.key_values = Collections.synchronizedMap(tmp_map) в reloadConfig?

Спасибо.

Ответы [ 3 ]

5 голосов
/ 11 ноября 2011

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

Синхронизация необходима в основном по двум причинам:

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

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

По вышеуказанной причине, вы должны синхронизировать весь доступ к карте: предположим, что поток пытается прочитать с него, в то время как другой поток вызывает reloadConfig().Произойдут плохие вещи.

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

// synchronizes on the in instance itself:
class MyDBConfig1 {
  // ...
  public synchronized String getProperty(...) { ... }
  public synchronized reloadConfig() { ... }
}

// synchronizes on a properly published, shared lock:
class MyDBConfig2 {
  private final Object lock = new Object();
  public String getProperty(...) { synchronized(lock) { ... } }
  public reloadConfig() { synchronized(lock) { ... } }
}

Правильная публикация здесь гарантировано ключевым словом final .Это незаметно: оно гарантирует, что значение этого поля будет видно каждому потоку после инициализации (без него поток может увидеть, что lock == null, и произойдут плохие вещи).

Вы могли бы улучшить кодвыше, используя (правильно опубликовано) ReadWriteReentrantLock.Это может немного улучшить параллелизм, если вас это беспокоит.

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

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

Тогда вам нужно только гарантировать, что каждый поток увидит правильные значения в хэш-карте.Для этого вы можете использовать те же методы, что и выше, или поле volatile, например:

class MyDBConfig {
  private volatile boolean initialized = false;
  public String getProperty(...) { if (initialized) { ... } else { throw ... } }
  private void reloadConfig() { ...; initialized = true; }
  public MyDBConfig(...) { ...; reloadConfig(); }
}

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

В приведенном выше коде вы пишете true в поле volatile после все значения были установлены.Затем метод чтения значений (getProperty(...)) начинается с выполнения энергозависимого чтения того же поля.Тогда этот метод гарантированно увидит правильные значения.

В приведенном выше примере, если вы не публикуете экземпляр до завершения конструктора, гарантируется, что исключение не будет вызвано в методе getProperty(...) (потому что до того, как конструктор завершит работу, вы написали true в initialized).

3 голосов
/ 11 ноября 2011
  1. Предполагая, что key_values ​​не будет put после reloadConfig, вам нужно будет синхронизировать доступ как к чтению, так и к записи карты. Вы нарушаете это, только синхронизируя на задании. Вы можете решить эту проблему, удалив синхронизированный блок и присвоив key_values ​​значение volatile.

  2. Поскольку HashMap эффективно только для чтения, я бы не назначил Collections.synchronizedMap, а Collections.unmodifiableMap (это не повлияло бы на саму карту, просто запретил бы случайные put s с кем-либо еще, возможно использующим этот класс ).

Примечание: Кроме того, вы никогда не должны синхронизировать поле, которое изменится. Результаты очень непредсказуемы.

Редактировать: Что касается других ответов. Настоятельно рекомендуется синхронизировать все совместно используемые изменяемые данные, так как эффекты недетерминированы. Поле key_values является общим изменяемым полем, и его назначения должны быть синхронизированы.

Редактировать: И чтобы устранить путаницу с Бруно Рейсом. Поле volatile будет допустимым, если вы по-прежнему заполняете tmp_map, а после его заполнения присвойте его this.key_values, оно будет выглядеть так:

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

  ..rest of class 

   public void reloadConfig() {
      Map<String, String> tmp_map = new HashMap<String,String> ();
      // read  data from database

      this.key_values = tmp_map;
   }

Вам все еще нужен тот же стиль, иначе Бруно Рейс отметил, что он не будет поточно-ориентированным.

0 голосов
/ 11 ноября 2011

Я бы сказал, что если вы гарантируете, что никакой код не будет структурно изменять вашу карту, то нет необходимости синхронизировать ее.

Если несколько потоков одновременно обращаются к хэш-карте, и хотя бы одиниз потоков модифицирует карту структурно, она должна быть синхронизирована извне.http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html

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

Поскольку ваш метод перезагрузки изменяет временную карту, а затем изменяет key_values, чтобы указать на новую карту, снова я бы сказал, что синхронизация не требуется.Худшее, что может случиться, это то, что кто-то читает со старой копии карты.

Я собираюсь держать голову опущенной и теперь ждать понижения голосов;)

РЕДАКТИРОВАТЬ

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

РЕДАКТИРОВАТЬ

Просто чтобы вернуться к конкретным вопросам, заданнымОП ...

  1. Предполагая, что свойства доступны только для чтения, я должен использовать синхронизацию в getProperty?
  2. Имеет ли смысл делать this.key_values ​​= Collections.synchronizedMap(tmp_map) в reloadConfig?

... Мне искренне интересно узнать, верны ли мои ответы.Поэтому я не буду сдаваться и удаляю свой ответ некоторое время;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...