Определение полей в параллельной среде

#java #multithreading #concurrency

#java #многопоточность #параллелизм

Вопрос:

У меня есть два метода в моем классе, которые будут выполняться в параллельной среде:

 class Clazz {

  private int counter = 0;
  private volatile Map<..> map = new ConcurrentHashMap<>();
  private int[] array;

  public void concurrentMethod() {
    ...perform some actions with map...
  }

  public int nonConcurrentMethod() {
    ...reinitialize the map...change references...
    counter  ;
    return array[counter];
  }

}
  

Вопрос заключается в следующем: предполагая, что nonConcurrentMethod это должно вызываться только одним потоком одновременно, должен ли я явно указывать counter и array как volatile поля? Создать счетчик atomic ?

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

Комментарии:

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

2. @MukeshVerma что, если я изменю ссылку и повторно инициализирую эту карту в nonConcurrentMethod ? Все другие потоки, активно использующие concurrentMethod , должны видеть обновленное поле

3. поскольку вы отметили это как окончательное, вы не можете переназначить ссылку.

4. @MukeshVerma виноват, описание исправлено

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

Ответ №1:

Обычно это весь класс, который имеет определенную потокобезопасную семантику, например, HashMap не является потокобезопасным, пока ConcurrentHashMap есть. Это особенно важно, если вы создаете библиотеку, люди могут обойти ваш дизайн, вызывая nonConcurrentMethod() из нескольких потоков.

IMO, если вы не можете разделить Clazz на два отдельных класса с разной потокобезопасной семантикой, было бы разумно сделать nonConcurrentMethod() потокобезопасным. В случае, если nonConcurrentMetho() вызывается из нескольких потоков, производительность снизится, но корректность будет сохранена, что, надеюсь, позволит избежать труднодоступных ошибок.

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

 private final Object lock = new Object();

public int nonConcurrentMethod() {
  synchronized(lock) {
    ...reinitialize the map...change references...
    counter  ;
    return array[counter];
  }
}
  

Убедитесь, что хотя бы одно из Clazz полей является final для обеспечения безопасной публикации.

Ответ №2:

Из информации, которую вы предоставили, индивидуальные изменения из map в concurrentMethod будут видны пользователям из в, но изменения, сделанные внутри вызова метода, не изолированы от другого вызова метода любого другого метода. Следовательно, если nonConcurrentMethod в какой-то момент clear() на карте concurrentMethod могут исчезнуть записи из map между вызовами методов на карте.

Например:

 concurrentMethod() {
    System.out.println(map.size());
    System.out.println(map.size());
}
  

Может выводить:

 10
0
  

If nonConcurrentMethod очищает сопоставление и вызывается во время concurrentMethod выполнения.

Если вы хотите обеспечить атомарный доступ, то вместо использования ConcurrentHashMap попробуйте использовать обычный HashMap и защитить его, защитив методы с помощью synchronized ключевого слова или используя более детальную блокировку, либо через synchronized(lock) { } , либо с помощью некоторых инструментов блокировки, предоставляемых в пакете Java concurrency, таких как ReadWriteLock интерфейс.

Ответ №3:

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

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

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


Даже если все методы в ConcurrentHashMap потокобезопасны, мы все равно должны позаботиться о том, чтобы обеспечить потокобезопасность, когда разные потокобезопасные методы вызываются несколькими потоками.

Например:

 Map map = new ConcurrentHashMap();

Thread t1 = new Thread();
Thread t2 = new Thread();

public void putIfAbsent(String key, String value) {
    if (!map.containsKey(key)) {
        map.put(key, value);
    }
}
  

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

Чтобы избежать такой ситуации, мы можем использовать два метода, упомянутых ниже;

  • Либо каждый вызывающий объект позаботится о том, чтобы сделать его потокобезопасным (на стороне клиента)
  • Или определите новый метод в одном месте для обеспечения потокобезопасности (предпочтительнее на стороне сервера)