#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
, и два потока вводят некоторые значения, если они отсутствуют.
Чтобы избежать такой ситуации, мы можем использовать два метода, упомянутых ниже;
- Либо каждый вызывающий объект позаботится о том, чтобы сделать его потокобезопасным (на стороне клиента)
- Или определите новый метод в одном месте для обеспечения потокобезопасности (предпочтительнее на стороне сервера)