Использование ключевого слова volatile с классами-оболочками

#java #multithreading #concurrency

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

Вопрос:

В классе по параллелизму Java мне посоветовали использовать следующий код для счетчика в многопоточном приложении

 private volatile int count;
 

Я спрашивал себя, могу ли я использовать ключевое слово volatile с классом-оболочкой Integer вместо примитивного типа int (см. Ниже):

 private volatile Integer count;
 

Было бы правильно использовать класс-оболочку Integer в этом случае?

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

1. Это законно, но неясно, что вы подразумеваете под «правильным». Вероятно, вам лучше использовать AtomicInteger .

Ответ №1:

На самом деле обе версии являются плохими проектами.

Из Java Concurrency на практике стр. 39:

…семантика volatile недостаточно сильна, чтобы сделать операцию приращения (count ) атомарной, если вы не можете гарантировать, что переменная записывается только из одного потока. (Атомарные переменные обеспечивают поддержку атомарного чтения-изменения-записи и часто могут использоваться как «более изменчивые переменные»)

Поэтому я рекомендую использовать AtomicInteger

 private AtomicInteger count;
 

Ответ №2:

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

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

 Thread 1: int temp = count.intValue(); //temp = 0;
Thread 2: int temp = count.intValue(); //temp = 0;
Thread 1: count = new Integer(temp 1); //count = 1;
Thread 2: count = new Integer(temp 1); //count = 1;
 

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

 count =  new Integer(count.intValue()   1);
 

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

Чтобы избежать этого, либо используйте an AtomicInteger (который не обязательно должен быть volatile), как предложено @chrylis, либо используйте синхронизацию и / или блокировки, чтобы убедиться, что у вас никогда не будет 2 потоков, записывающих счетчик.

Ответ №3:

Пометка как volatile верна только в том случае, если единственное, что вы делаете за пределами синхронизированной области, — это установка или получение значения. ЛЮБАЯ попытка «относительной» математики (увеличение, уменьшение и т.д.) Не является потокобезопасной. Для выполнения любой такой работы требуется либо синхронизация, либо использование AtomictInteger .

Ответ №4:

Класс Integer является неизменяемым, поэтому при изменении count он получает ссылку на новое целое число, а ключевое слово volatile гарантирует, что новая ссылка видна во всех потоках.

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