#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 было бы лучшим выбором, потому что в противном случае увеличение на основе текущего значения будет небезопасным.