Подтвердить что ты не робот

Как JVM внутренне справляется с условиями гонки?

Если несколько потоков пытаются обновить одну и ту же переменную-член, это называется условием гонки. Но меня больше интересовало, как JVM обрабатывает его внутренне, если мы не будем обрабатывать его в нашем коде, сделав его синхронизированным или что-то еще? Будет ли это зависать моя программа? Как будет реагировать JVM на это? Я думал, что JVM временно создаст блок синхронизации для этой ситуации, но я не уверен, что именно произойдет.

Если у кого-то из вас есть понимание, было бы хорошо знать.

4b9b3361

Ответ 1

Точный термин - гонка данных, которая является специализацией общей концепции состояния расы. Термин "гонка данных" является официальной четко определенной концепцией, что означает, что она возникает из формального анализа кода.

Единственный способ получить реальную картину - перейти к изучению главы модели памяти Спецификации языка Java, но это упрощенное представление: всякий раз, когда у вас есть гонка данных, почти нет гарантии относительно результата и поток чтения может видеть любое значение, которое когда-либо было записано в переменную. В этом и заключается единственная гарантия: нить не будет воспринимать значение "вне тонкого воздуха", которое никогда не было написано. Ну, если вы не имеете дело с long или double s, то вы можете увидеть порванные записи.

Ответ 2

Может быть, мне что-то не хватает, но что с этим нужно справиться? По-прежнему есть поток, который попадет туда первым. В зависимости от того, какой поток есть, этот поток будет просто обновлять/читать некоторую переменную и перейти к следующей инструкции. Он не может магически построить блок синхронизации, он действительно не знает, что вы хотите сделать. То есть, другими словами, то, что произойдет, будет зависеть от результата "расы".

Примечание. Я не сильно разбираюсь в материалах более низкого уровня, поэтому, возможно, я не полностью понимаю глубину вашего вопроса.

Ответ 3

Java предоставляет synchronized и volatile для решения этих ситуаций. Использование их должным образом может быть довольно сложно, но имейте в виду, что Java только раскрывает сложность современных архитектур процессора и памяти. Альтернативами было бы всегда ошибаться на стороне осторожности, эффективно синхронизировать все, что могло бы убить производительность; или игнорировать проблему и не предлагать никакой безопасности потока. И, к счастью, Java предоставляет превосходные конструкторы высокого уровня в пакете java.util.concurrent, поэтому вы можете часто избегать использования низкоуровневых материалов.

Ответ 4

JVM отлично справится с ситуацией (т.е. не будет висеть или жаловаться), но вы не можете получить результат, который вам нравится!

Когда задействовано несколько потоков, java становится дьявольски сложным, и даже код, который выглядит явно правильным, может оказаться ужасно нарушенным. В качестве примера:

public class IntCounter {
    private int i;

    public IntCounter(int i){
         this.i = i;
    }

    public void incrementInt(){
        i++;
    }

    public int getInt(){
        return i;
    }
}

имеет недостатки во многих отношениях.

Во-первых, скажем, что я в настоящее время 0, а поток A и поток B оба вызова incrementInt() примерно в одно и то же время. Существует опасность того, что они оба увидят, что я равно 0, затем оба увеличивают его 1, а затем сохраняют результат. Итак, в конце двух вызовов я только 1, а не 2!

Что проблема состояния гонки с кодом, но есть и другие проблемы, касающиеся видимости памяти. Когда поток A изменяет общую переменную, нет гарантии (без синхронизации), что нить B когда-либо увидит изменения!

Таким образом, поток A может увеличиваться я 100 раз, а через час поток B, вызывающий getInt(), может видеть я как 0 или 100 или где-нибудь посередине!

Единственная нормальная вещь, которую нужно сделать, если вы вникаете в java concurrency, - это прочитать Java concurrency на практике Brian Goetz et al. (Хорошо, возможно, есть и другие хорошие способы узнать об этом, но это отличная книга, написанная Джошуа Блохом, Дугом Ли и другими). ​​

Ответ 5

Короче говоря, JVM предполагает, что код не содержит данных, когда он переводится в машинный код. То есть, если код неправильно синхронизирован, спецификация языка Java предоставляет только ограниченные гарантии поведения этого кода.

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

В частности, спецификация языка Java гарантирует следующее только при отсутствии гонки данных:

  • видимость: чтение поля дает значение, присвоенное ему последнему (непонятно, какая запись была последней, и пишет о длинных или двойных переменных не обязательно атомарно)
  • ordering: если запись видна, то есть любые записи, предшествующие ей. Например, если выполняется один поток:

    x = new FancyObject();
    

    другой поток может читать x только после того, как конструктор FancyObject выполнил полностью.

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

Однако гонка данных не нарушит целостность виртуальной машины Java. В частности, JVM не будет терпеть крах или остановиться и по-прежнему гарантировать безопасность памяти (т.е. Предотвратить повреждение памяти) и определенную семантику конечных полей.