Есть ли основания не ВСЕГДА использовать 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 и друзья.

Если у вас есть эффективно неизменным ints вы можете обойтись без обеспечения синхронизации за счет ее расчета. Примером является 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

Другие вопросы по тегам