66

Сегодня встретил такой код

class someClass {

// ...
private volatile int a; // ...

}

Вопрос в том, что такое volatile в данном контексте?

AvidCoder
  • 2,808
Nicolas Chabanovsky
  • 51,426
  • 87
  • 267
  • 507
  • ассоциация: http://stackoverflow.com/questions/3430757/ – Nofate Feb 27 '17 at 18:09
  • Пожалуйста, почитайте спецификацию языка и никогда не говорите про кеширование переменных. Полезные ссылки: 1. https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html 2. https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 3. https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4 – bigspawn Sep 18 '19 at 13:33

7 Answers7

70

Модификатор volatile накладывает некоторые дополнительные условия на чтение/запись переменной. Важно понять две вещи о volatile переменных:

  1. Операции чтения/записи volatile переменной являются атомарными.
  2. Результат операции записи значения в volatile переменную одним потоком, становится виден всем другим потокам, которые используют эту переменную для чтения из нее значения.

Кажется, что для человека, задающего вопрос вроде вашего, достаточно знать эти два момента.

a_gura
  • 13,169
  • насчёт объектов: слово volatile запрещает создавать доп. ссылки в сторонних потоках ? а т.к. все эти ссылки ссылаются на один и тот же объект то нет ниченго плохого в том чтобы плодить эти ссылки , следовательно для объектным ссылок volatile можно не писать. я прав? – arg Apr 20 '14 at 19:56
  • 4
    Читайте хотя бы википедию: "даже модификатор volatile переменной не означает реальной необходимости применения атомарных операций". – huffman Apr 20 '14 at 20:23
  • @huffman это вы мне или автору вопроса? :) – a_gura Apr 20 '14 at 20:32
  • @argamidon нет, неправы. Модификатор volatile ничего не запрещает. Вообще все завсиит от задачи. Присваивание значения ссылке в JVM - атомарно. Но это не решает проблемы с ошибками видимости в других потоках. Поэтому нужен volatile, ну или AtomicRef. – a_gura Apr 20 '14 at 20:37
  • т.е. volatile с объектными ссылками нужен тогда, когда я из других потоков присваиваю значение общей используемой ссылке? – arg Apr 21 '14 at 04:32
  • volatile и другие механизмы синхронизации вообще нужны только если вы работаете с разделяемым между разными потоками состоянием. – a_gura Apr 21 '14 at 06:19
  • @a_gura, но операции чтения/записи volatile переменной вроде не обязательно являются атомарными, разве нет?.. Например: "использование volatile имеет ограничение для некоторых редких случаев, когда необходимо обеспечить атомарность" (из статьи https://idurdyev.com/volatile-in-java) – Ksenia Feb 21 '18 at 16:15
  • @Ksenia В этом месте статьи речь идет о составных операциях, а не об отдельных операциях чтения/записи. – a_gura Feb 21 '18 at 16:32
  • Операция чтения и записи не атомарные? – Alex78191 Feb 26 '20 at 13:33
  • @Alex78191 На момент обсуждения JLS не гарантировала атомарную запись/чтение для long и double [JLS, 17.4.7]. Как минимум это было верно для Java 8, которая до сих пор поставляется в 32-битной сборке. Что там с более поздними версиями языка и спецификации нужно смотреть отдельно. – a_gura Mar 03 '20 at 17:27
  • @Alex78191 В общем гарантий нет: https://docs.oracle.com/javase/specs/jls/se13/html/jls-17.html#jls-17.7

    Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.

    – a_gura Mar 03 '20 at 17:29
  • Результат операции записи значения в volatile переменную одним потоком, становится виден ... - верно, но нужно дополнить Результат операции записи значения в volatile переменную одним потоком, а так же все результаты записи в другие переменные сделанные до записи в volatile переменную этим же потоком, становятся видны ... – Roman-Stop RU aggression in UA Nov 26 '20 at 14:10
  • @a_gura, вы пишите Операции чтения/записи volatile переменной являются атомарными. - безопасными атомарными операциями являются чтение и присвоение примитивов, кроме long и double. Атомарность можно обеспечить, определяя переменные long и double с ключевым словом volatile. – West Side May 31 '21 at 15:45
42

Это означает, что значение переменной будет "всегда читаться". Например, в многопоточных приложениях один поток прочёл значение a=1, передал управление другому потоку, который изменил значение на a=2, потом управление вернулось. Так вот, без volatile значение a у первого потока будет 1, т.к. первый поток "помнит", что a=1, с volatile - 2, т.к. первый поток снова прочтет значение и получит уже измененное.

Georgy
  • 1,884
  • 11
  • 13
  • Как понять "всегда читаться"? Оно что, константное? Почему тогда просто не написать final? – Nicolas Chabanovsky Jan 10 '11 at 18:09
  • 24
    Нет, не константное. volatile как бы говорит компилятору, что значение переменной может в любой момент измениться из другого потока и даже из другой программы, а он этого и не заметит. Поэтому компилятор прекращает выполнять различную оптимизацию, связанную с этой переменной, (к примеру, помещение копии в регистры) и всегда читает ее значение из памяти. – ctor Jan 10 '11 at 19:37
  • 1
    Не концентрируйтесь так на чтении. Запись тоже выполняется сразу в память и, если не ошибаюсь, на SMP-системах необходимо сбрасывать кэш, иначе другое ядро может не получить своевременно результат изменений, даже если будет читать из памяти. – cy6erGn0m Jan 20 '11 at 16:22
  • я видел много примеров volatile с примитивами, но не видел его с объектами. Нужно ли использовать volatile с объектами? или у объектов локальные копии не создаются?? – arg Apr 20 '14 at 18:34
  • 3
    Не у объектов, а у ссылок. Ваша переменная ссылается на объект. Соответственно использовать volatile имеет смысл когда вы присвоили переменной ссылку на другой объект в другом потоке. – a_gura Apr 20 '14 at 19:29
34
  • у переменной есть мастер копия плюс по копии на каждую нить, что её используют. Мастер копия синхронизируется с локальной копией нити при входе/выходе в/из блока synchronized. Иногда, например, пустой блок synchronized(lock){} имеет смысл.

  • у переменных с модификатором volatile локальных копий нет. Все нити работают с мастер копией.

bbk75
  • 357
  • 2
  • 5
  • Верное, но очень сложное для новичков объяснение. – cy6erGn0m Jan 20 '11 at 16:19
  • 6
    упрощение приводит к артефактам в коде.

    Ещё надо отметить, что volatile не панацея при кодировании нескольких нитей: если используются неатомарные операции, например += или ++

    – bbk75 Jan 20 '11 at 22:06
  • Не упрощение приводит к ним, а непонимание. Но никто не совершенен. Нельзя с места, да в карьер :) – cy6erGn0m Jan 21 '11 at 00:21
  • Сильно программа замедляется, если не использовать локальную копию? – Alex78191 Feb 26 '20 at 13:36
  • Вот это объяснение спасибо! Теперь скажите пожалуйста что прочитать, чтобы эти знания получить в полном ввиде, а не форума черпать?) – Георгий Чеботарев Apr 13 '23 at 06:50
21

Вот какое определение дается в статье «Многопоточность Java» на сайте http://alfalavista.ru.

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

В целях повышения производительности спецификация языка Java допускает сохранение в JRE локальной копии переменной для каждого потока, который на нее ссылается. Такие "локальные" копии переменных напоминают кэш и помогают потоку избежать обращения к главной памяти каждый раз, когда требуется получить значение переменной. При запуске двух потоков один из них считывает переменную A как 5, а второй ― как 10. Если значение переменной А изменилось с 5 на 10, то первый поток не узнает об изменении и будет хранить неправильное значение A. Но если переменная А помечена как volatile, то когда бы поток не считывал значение A, он будет обращаться к главной копии A и считывать ее текущее значение. Локальный кэш потока имеет смысл в том случае, если переменные в ваших приложениях не будут изменяться извне.

Если переменная объявлена как volatile, это означает, что она может изменяться разными потоками. Естественно ожидать, что JRE обеспечит ту или иную форму синхронизации таких volatile-переменных. JRE действительно неявно обеспечивает синхронизацию при доступе к volatile-переменным, но с одной очень большой оговоркой: чтение volatile-переменной и запись в volatile-переменную синхронизированы, а неатомарные операции ― нет.

Nick Volynkin
  • 34,094
tarasula
  • 792
5

для объектным ссылок volatile можно не писать. я прав?

Например, когда мы в многопоточном приложении используем паттерн Синглтон в котором применяем синхронизацию и хотим чтобы синхронизация осуществлялась только один раз при инициализации объекта, а не каждый раз, когда мы вызываем getInstance(), тогда модификатора volatile используем для объектной ссылки:

public class Singleton {
    private static volatile Singleton instance;
    private Singleton(){
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
Nicolas Chabanovsky
  • 51,426
  • 87
  • 267
  • 507
5

volatile - буквально означает летучий, непостоянный, изменчивый

в контексте программирования это означает, что значение переменной может неожиданно изменяться, поэтому не стоит полагаться на значения этой переменной, например, если в коде написано:

private volatile int i;
// через некоторое время
i=0;
while(i < 10) {
  //blah-blah
  i++;
}

это не означает, что цикл точно завершится через 10 шагов...

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

Barmaley
  • 81,300
  • Интересное наблюдение. Я назвал бы это пример антишаблоном использования volatile. В данном случае ошибочно синхронизирована между потоками переменная, которая должна наоборот быть локальной, независимой от других потоков. – RoutesMaps.com Feb 11 '19 at 17:35
  • Не все так просто - обычно такая история происходит когда значение volatile определяется например каким-нибудь драйвером/датчиком чего-нибудь, который не поддается синхронизации средствами Java – Barmaley Feb 12 '19 at 06:16
  • Да, Вы правы, при необходимости объявить общую для всех потоков переменную volatile надо предвидеть возможное её изменение другим потоком и не применять её для местных потребностей потоков. – RoutesMaps.com Feb 12 '19 at 07:27
5

volatile - говорит потоку что переменная может меняться, и информирует поток о необходимости обращаться к последней версии, а не к хешированной копии и своевременно распространять изменения.

A "volatile" data member informs a thread, both to get the latest value for the variable (instead of using a cached copy) and to write all updates to the variable as they occur.