Volatile против статики в Java

265

Правильно ли говорить, что это staticозначает одну копию значения для всех объектов и volatileозначает одну копию значения для всех потоков?

В любом случае, staticзначение переменной также будет одним значением для всех потоков, тогда зачем нам идти volatile?

Jothi
источник
Официальное объяснение изменчивости: cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile
Вадим

Ответы:

365

Объявление статической переменной в Java означает, что будет только одна копия, независимо от того, сколько объектов класса создано. Переменная будет доступна даже без Objectsсоздания вообще. Однако потоки могут иметь локально кэшированные значения этого.

Когда переменная изменчива, а не статична , для каждой будет одна переменная Object. Таким образом, на первый взгляд, нет никакой разницы от обычной переменной, но она полностью отличается от статической . Однако даже с Objectполями поток может кэшировать значение переменной локально.

Это означает, что если два потока одновременно обновляют переменную одного и того же объекта, а переменная не объявляется как volatile, может быть случай, когда один из потоков имеет в кэше старое значение.

Даже если вы обращаетесь к статическому значению через несколько потоков, каждый поток может иметь свою локальную кэшированную копию! Чтобы избежать этого, вы можете объявить переменную как static volatile, и это заставит поток читать каждый раз глобальное значение.

Тем не мение, volatile не заменяет правильную синхронизацию!
Например:

private static volatile int counter = 0;

private void concurrentMethodWrong() {
  counter = counter + 5;
  //do something
  counter = counter - 5;
}

Выполнение concurrentMethodWrongодновременно много раз может привести к конечному значению счетчика , отличным от нуля!
Чтобы решить проблему, вы должны реализовать блокировку:

private static final Object counterLock = new Object();

private static volatile int counter = 0;

private void concurrentMethodRight() {
  synchronized (counterLock) {
    counter = counter + 5;
  }
  //do something
  synchronized (counterLock) {
    counter = counter - 5;
  }
}

Или используйте AtomicIntegerкласс.

stivlo
источник
7
Модификатор volatile гарантирует, что любой поток, который читает поле, увидит последнее записанное значение, поэтому необходимо, если переменная является общей для нескольких потоков, и вам нужна эта функция, это зависит от вашего варианта использования.
stivlo
5
Что такое кеш, когда вы говорите «локально кешируется»? Кеш процессора, какой-то кеш JVM?
Mert Inan
6
@mertinan да, переменная может находиться в кеше ближе к процессору или ядру. См. Cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html для получения более подробной информации.
stivlo
15
«volatile» не подразумевает «одну переменную на объект». Отсутствие «статики» делает это. -1 за неясность этого элементарного заблуждения со стороны ОП.
Маркиз Лорн
27
@EJP Я думал, что предложение «Объявляя переменную как volatile, для каждого объекта будет одна переменная. Так что на первый взгляд кажется, что нет никакой разницы от нормальной переменной» объясняло, что я добавил, а не статично , не стесняйтесь редактировать статью и улучшать формулировку, чтобы сделать это более понятным.
stivlo
288

Разница между статическим и изменчивым:

Статическая переменная : если два потока (предположим , t1и t2) имеют доступ к той же объект и обновление переменной , которая объявлена как статические , то это означает , что t1и t2может сделать свою собственную локальную копию одного и того же объекта ( в том числе статические переменные) в их соответствующем кэше, поэтому обновление сделанный t1статической переменной в ее локальном кэше, не будет отражаться в статической переменной для t2кэша.

Статические переменные используются в контексте объекта, где обновление, выполненное одним объектом, будет отражаться во всех других объектах того же класса, но не в контексте потока, где обновление одного потока до статической переменной будет отражать изменения сразу для всех объектов. потоки (в их локальном кэше).

Летучие переменный : если два потока (предположит , t1и t2) имеют доступ к тому же объекту и обновление переменного , которая объявлена как летучие то это означает , что t1и t2может сделать свой собственный локальный кэш объекта , за исключением переменного , которая объявлена как летучие . Таким образом, переменная volatile будет иметь только одну основную копию, которая будет обновляться различными потоками, а обновление, выполненное одним потоком для переменной volatile, будет немедленно отражено для другого потока.

Som
источник
6
Здравствуйте @Som, Пожалуйста, поправьте меня, если я ошибаюсь. Но вы не думаете, что утверждение « но не в контексте Thread, где обновление одного потока до статической переменной будет отражать изменения сразу для всех потоков (в их локальном кэше). « Должно быть », но не в контексте потока, где обновление одного потока до статической переменной будет << НЕ >> отражать изменения сразу для всех потоков (в их локальном кэше). "
Джайкрат
@Jaikrat Да, это меня очень смущало. Насколько я понимаю, вы правы и этот ответ неверен, как написано. Я также хотел бы быть исправленным, если я неправ.
Стюарт
@Jaikrat Потоки не кэшируют статические переменные, но обращаются к обновленным статическим переменным.
Сом
@Som Тогда вы хотели бы исправить параграф и удалить, но не в контексте Thread . Это очень запутанно. Спасибо
Jaikrat
К сожалению, этот ответ неверен. На современных процессорах даже volatileпеременная может быть разделена между различными кэшами процессора. Это не представляет проблемы, потому что кеш согласовывает исключительное владение строкой кеша до ее изменения.
Дэвид Шварц
32

В дополнение к другим ответам, я хотел бы добавить для него одно изображение (рис легко понять)

введите описание изображения здесь

staticпеременные могут быть кэшированы для отдельных потоков. В многопоточной среде, если один поток изменяет свои кэшированные данные, это может не отражаться на других потоках, поскольку они имеют их копию .

volatileОбъявление гарантирует, что потоки не будут кэшировать данные и использует только общую копию .

источник изображения

mrsrinivas
источник
1
статические переменные делятся между объектами в потоке? При этом следует читать статические переменные, разделяемые между объектами всеми объектами независимо от потоков.
cquezel
1
msgstr "изменчивые переменные распределяются между несколькими потоками (как и объектами)." Volatile не изменяет распределение переменных между несколькими потоками или объектами. Это меняет способ выполнения кэширования значения.
cquezel
1
Ваш комментарий о статических переменных также относится к нестатическим, и слова «будут кэшированы» и «не будут отражать», вероятно, должны быть перефразированы «могут быть кэшированы» и «могут не отражать».
cquezel
4
Я был очень смущен. эта картина очистила все мои вопросы!
Вин
5

Я думаю staticи не volatileимею никакого отношения вообще. Я предлагаю вам прочитать Java-учебник, чтобы понять атомарный доступ , и зачем использовать атомарный доступ, понять, что чередуется , вы найдете ответ.

Амитабха
источник
4

Проще говоря,

  1. статические : staticпеременные связаны с классом , а не с любым объектом . Каждый экземпляр класса совместно использует переменную класса, которая находится в одном фиксированном месте в памяти

  2. volatile : это ключевое слово применимо к переменным класса и экземпляра .

Использование энергозависимых переменных снижает риск ошибок согласованности памяти, потому что любая запись в энергозависимую переменную устанавливает связь «происходит до» с последующим чтением этой же переменной. Это означает, что изменения в изменчивой переменной всегда видны другим потокам

Взгляните на эту статью , Javin Paul чтобы лучше понять переменные переменные.

введите описание изображения здесь

При отсутствии volatileключевого слова значение переменной в стеке каждого потока может отличаться. Делая переменную какvolatile , все потоки получат одинаковое значение в своей рабочей памяти, и ошибок целостности памяти удалось избежать.

Здесь термин variableможет быть либо static(классом) переменной, либоinstance (объектной) переменной.

По вашему запросу:

В любом случае, значение статической переменной также будет одним значением для всех потоков, тогда почему мы должны использовать volatile?

Если мне нужно instance переменная в моем приложении, я не могу использовать staticпеременную. Даже в случаеstatic переменной согласованность не гарантируется из-за кеша потоков, как показано на диаграмме.

Использование volatileпеременных снижает риск ошибок согласованности памяти, потому что любая запись в энергозависимую переменную устанавливает связь «произойдет до» с последующим чтением этой же переменной. Это означает, что изменения в изменчивой переменной всегда видны другим потокам.

Более того, это также означает, что когда поток читает переменную volatile, он видит не только последние изменения volatile, но также побочные эффекты кода, который привел к изменению => ошибки согласованности памяти все еще возможны с volatile переменными , Чтобы избежать побочных эффектов, вы должны использовать синхронизированные переменные. Но есть лучшее решение в Java.

Использование простого атомарного доступа к переменным более эффективно, чем доступ к этим переменным через синхронизированный код

Некоторые из классов в java.util.concurrent пакете предоставляют атомарные методы, которые не зависят от синхронизации.

Обратитесь к этому высокоуровневому контролю параллелизма статье о для получения дополнительной информации.

Особенно взгляните на атомарные переменные .

Связанные вопросы SE:

Летучие против атомных

Летучий логический против AtomicBoolean

Разница между изменчивым и синхронизированным в Java

Равиндра Бабу
источник
Я действительно ценю этот ответ. Я знал , что это volatileраньше, но этот ответ проясняет много для меня , почему я до сих пор нужно использовать volatileс staticпеременной.
Chaklader Asfak Arefe
volatile: это ключевое слово применимо к переменным класса и экземпляра. Вышеупомянутое утверждение неверно в отношении к классу. только два ключевых слова, которые применимы к переменной, являются изменчивыми и преходящими. такой изменчивый, неприменим для класса.
ASR
volatile применима к классовым (статическим) переменным. Проверьте наличие двойных заблокированных одноэлементных ссылок в Google, и вы обнаружите, что ваше понимание неверно. stackoverflow.com/questions/18093735/…
Равиндра Бабу
private static volatile является действительным объявлением.
Равиндра Бабу
0

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

Теперь, если статическая переменная используется в многопоточной среде, возникнут проблемы, если от нее ожидать желаемого результата. Поскольку каждый поток имеет свою собственную копию, то любое увеличение или уменьшение статической переменной из одного потока может не отражаться в другом потоке.

если ожидаются желаемые результаты от статической переменной, тогда используйте volatile со static в многопоточности, тогда все будет решено.

Аслам Анвер
источник
0

Не уверен, что статические переменные кэшируются в локальной памяти потока или НЕ. Но когда я выполнил два потока (T1, T2), обращающихся к одному и тому же объекту (obj), и когда обновление, выполненное потоком T1, до статической переменной, это отразилось в T2.

user2779355
источник
-2

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

Если переменная объявлена ​​как volatile, все потоки будут иметь свою собственную копию переменной, но значение будет взято из основной памяти. Так что значение переменной во всех потоках будет одинаковым.

Таким образом, в обоих случаях главное состоит в том, что значение переменной одинаково во всех потоках.

Джитендра Налвея
источник
15
Если переменная объявлена ​​как volatile, все потоки будут иметь свою собственную копию переменной, но значение берется из основной памяти. => верно. Таким образом, значение переменной во всех потоках будет одинаковым. => неправильно, каждый поток будет использовать одно и то же значение для одного и того же объекта, но каждый объект будет иметь свою собственную копию.
stivlo