Обычно мы используем volatile для реализации некоторого кода, требующего потокобезопасности (многие люди не осмеливаются его использовать, потому что не понимают его), но на самом деле volatile сам по себе не является потокобезопасным, по сравнению с синхронизированным, у него больше ограничений на использование, только могут быть ограничены определенными конкретными сценариями. Цель этой статьи - дать каждому понять суть volatile Для достижения этой цели мы объясним модель памяти Java и управление памятью операций с переменными (не бойтесь, вы найдете это очень просто).
1. Модель памяти
Память можно просто разделить на два типа: оперативная память и основная память. Все данные в конечном итоге должны храниться в основной памяти, рабочая память уникальна для потоков, и между потоками нет помех. Модель памяти Java в основном определяет взаимодействие между рабочей памятью и основной памятью, то есть то, как рабочая память копирует данные из основной памяти в способ записи данных. Java определяет 8 атомарных операций для завершения взаимодействия между рабочей памятью и основной памятью:
- lock переводит объект в потоко-эксклюзивное состояние
- разблокировка снимает блокировку объекта в исключительном состоянии потока
- read читает данные из основной памяти
- load записывает данные, прочитанные из основной памяти, в рабочую память
- использовать объект использования рабочей памяти
- assign назначает объект в рабочей памяти
- store переносит объекты из рабочей памяти в основную память
- write записывает объект в основную память, перезаписывая старое значение
Эти операции также выполняются при соблюдении определенных условий:
Чтение и загрузка, сохранение и запись должны стоять парами, то есть рабочая память должна принимать данные, считанные из основной памяти, данные, переданные в основную память, не могут быть отвергнуты для записи.
Назначенный объект должен быть записан обратно в кеш
Объекты, которые не были назначены заново, не могут быть записаны обратно в основную память.
Новые переменные можно создавать только в основной памяти, а неинициализированные объекты нельзя использовать в рабочей памяти.
Объекты могут быть заблокированы только одним потоком и могут быть заблокированы этим потоком несколько раз.
Разблокированные объекты не могут выполнять операцию разблокировки
Прежде чем разблокировать объект, объект должен быть записан обратно в основную память.
Восемь атомарных операций java имеют определенные ограничения друг перед другом, но нет строгого ограничения, что любые две операции должны появляться последовательно, а только парами, из-за чего возникает небезопасность потока.
Представляя приведенные выше базовые знания, давайте посмотрим на разницу между volatile-переменными и обычными переменными.
Во-вторых, изменчивые переменные и обычные переменные
2.1 Безопасность летучих
Ниже мы используем пример, чтобы проиллюстрировать разницу между volatile переменными и обычными переменными.
Предположим, что есть два потока, обрабатывающих объект в основной памяти, и поток 1 начинается раньше, чем поток 2 (в следующем примере показана операция a++))
public class ThreadSafeTest {
public static int a = 0;
public static void increase() {
a++;
}
public static void main (String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
t1.start();
t2.start();
}
}
Когда поток 2 читает объект основной памяти (а), это может происходить в несколько периодов: до чтения, после чтения, после загрузки, после использования, после назначения, после сохранения и после записи (как показано на рисунке ниже);
Предполагая, что поток 1 выполняет a++, a изменяется с 0 на 1, и нет времени для обратной записи в объект основной памяти, данные, считанные потоком 2 из объекта основной памяти a=0; в это время поток 1 записывает в основную память a=1 , а поток 2 все еще выполняет a++, который в это время все еще равен 1 (должен быть равен 2). Фактически, это эквивалентно чтению потоком 2 данных с истекшим сроком действия, что приводит к потоку незащищенность.
Правильно ли превратить a в volatile объект?
Volatile накладывает более строгие ограничения на операции с объектами:
- Не читать и не загружать перед использованием
-
за назначением должно следовать сохранение и запись
На самом деле это эквивалентно превращению трех атомарных операций чтения и загрузки в одну атомарную операцию, превращению назначения-сохранения-записи в одну атомарную операцию. Во многих статьях говорится, что volatile виден всем потокам, а это означает, что основная память будет записана обратно сразу после выполнения assign; когда какой-либо поток читает объект основной памяти, основная память будет обновлена. Данные согласованы в основной памяти, но не обязательно согласованы в памяти каждого потока.
Тот же самый код выше, замененный на volatile
```
public class ThreadSafeTest {
public static volatile int a = 0;public static void increase() {
a++;
}
public static void main (String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
increase();
}
}
});
t1.start();
t2.start();
}
}
```
После запуска обнаруживается, что правильный результат не может быть получен (если вы его получили, пожалуйста, увеличьте значение j). Пошел ты, разве это не потокобезопасная переменная? Почему это не правильно?
Это связано с тем, что в потоке все еще могут быть несогласованности данных. Например, если поток 2 читает данные, это происходит после использования потоком 1, но поток 1 еще не успел записать обратно в основной кеш. , поток 2 использует данные по-прежнему 0, и два потока одновременно нацелены на 0++, и результатом будет только 1, а не идеальное 2.
2.2 Потокобезопасность volatile является условной
Теперь, когда volatile не является потокобезопасным, какой от него прок? Если вы читали написанную мной статью о безопасности потоков, то должны знать, что все объекты относительно потокобезопасны, то есть условно. Потокобезопасность volatile, конечно, условна, это дополнение к тяжеловесной синхронизации потоков synchronized, и его общая производительность лучше, чем у synchronized. Каковы условия для потокобезопасности volatile? В каких сценариях он подходит для использования?
«виртуальная машина Java» дает два условия:
- Результат операции не зависит от текущего значения переменной (то есть результат не зависит от генерации промежуточных результатов) или может гарантировать, что только один поток изменяет значение переменной
- Переменные не должны участвовать в инвариантных ограничениях с другими переменными состояния (я думаю, что это лишнее, и эта другая переменная также должна быть потокобезопасной)
Для каких сценариев это подходит? Я не буду приводить примеры этого один за другим. Брат очень хорошо подытожил. Ссылка выглядит следующим образом:Woohoo. IBM.com/developer Я…