Есть ли основания не ВСЕГДА использовать AtomicInteger в качестве членов данных?
В многопоточной среде, такой как Android, где простой int
переменной можно манипулировать несколькими потоками, есть ли обстоятельства, при которых все еще оправдано использование int
как член данных?
int
как локальная переменная, ограниченная областью действия метода, который имеет монопольный доступ к нему (и, следовательно, начало и конец изменения всегда находятся в одном потоке), имеет смысл с точки зрения производительности.
Но в качестве члена данных, даже если он обернут в метод доступа, он может столкнуться с хорошо известной проблемой одновременной модификации с чередованием.
Таким образом, похоже, что "осторожно" можно просто использовать AtomicInteger
пересечь границу. Но это кажется ужасно неэффективным.
Можете ли вы привести пример многопоточного int
использование элемента данных?
4 ответа
Есть ли основания не ВСЕГДА использовать AtomicInteger в качестве членов данных?
Да, есть веские причины не всегда использовать AtomicInteger
, AtomicInteger
может быть как минимум на порядок медленнее (возможно, больше) из-за volatile
построить, чем местный int
и другие Unsafe
конструкции, используемые для установки / получения базового int
значение. volatile
означает, что вы пересекаете барьер памяти каждый раз, когда вы получаете доступ к AtomicInteger
что вызывает очистку кеш-памяти на рассматриваемом процессоре.
Кроме того, только потому, что вы сделали все свои поля AtomicInteger
не защищает вас от условий гонки при доступе к нескольким полям. Там просто нет замены для принятия правильных решений о том, когда использовать volatile
, synchronized
и Atomic*
классы.
Например, если у вас есть два поля в классе, к которым вы хотите получить надежный доступ в поточной программе, вы должны сделать что-то вроде:
synchronized (someObject) {
someObject.count++;
someObject.total += someObject.count;
}
Если оба из тех членов с AtomicInteger
тогда вы получите доступ volatile
вдвое пересекая 2 барьера памяти вместо одного. Кроме того, назначения выполняются быстрее, чем Unsafe
операции внутри AtomicInteger
, Кроме того, из-за условий гонки данных с двумя операциями (в отличие от synchronized
блоки выше) вы можете не получить правильные значения для total
,
Можете ли вы привести пример многопоточного использования членов данных int?
Помимо того, чтобы сделать это final
нет механизма для поточно-ориентированного int
элемент данных за исключением маркировки volatile
или используя AtomicInteger
, Не существует волшебного способа нарисовать потокобезопасность на всех ваших полях. Если бы было тогда, программирование потока было бы легко. Задача состоит в том, чтобы найти правильные места для synchronized
блоки. Чтобы найти правильные поля, которые должны быть отмечены volatile
, Чтобы найти правильные места для использования AtomicInteger
и друзья.
Если у вас есть эффективно неизменным int
s вы можете обойтись без обеспечения синхронизации за счет ее расчета. Примером является hashCode
int hash = 0;
public int hashCode(){
if(hash == 0){
hash = calculateHashCode(); //needs to always be the same for each Object
}
return hash;
}
Очевидным компромиссом здесь является возможность многократных вычислений для одного и того же хеш-значения, но если альтернативой является synchronized
hashCode, который может иметь гораздо худшие последствия.
Это технически потокобезопасно, хотя и избыточно.
Это зависит от того, как оно используется в отношении. другие данные. Класс инкапсулирует поведение, поэтому часто переменная почти бессмысленна без других. В таких случаях может быть лучше защитить (*) элементы данных, которые принадлежат друг другу (или весь объект), а не только одно целое число. Если вы сделаете это, то AtomicInteger
ненужный удар по производительности
(*) с использованием общих механизмов обеспечения безопасности потоков: мьютекс, семафор, монитор и т. д.
Безопасность потоков - это не только присваивания atomic int, вам нужно тщательно проектировать шаблоны блокировок, чтобы обеспечить согласованность кода.
Если у вас есть два Account
занятия с публичными участниками Balance
рассмотрим следующий простой код.
Account a;
...
int withdrawal = 100;
if(a.Balance >= withdrawal)
{
// No atomic operations in the world can save you from another thread
// withdrawing some balance here
a.Balance -= withdrawal
}
else
{
// Handle error
}
Быть действительно откровенным. В реальной жизни наличие атомарных заданий редко бывает достаточно, чтобы решить мои проблемы параллелизма в реальной жизни.
Я предполагаю, что Google увидел OP и обновил свою документацию по этому вопросу, чтобы быть более ясным:
"AtomicInteger используется в таких приложениях, как счетчики с атомарным приращением, и не может использоваться в качестве замены Integer".
https://developer.android.com/reference/java/util/concurrent/atomic/AtomicInteger