Ключевое слово volatile в Java

  • Синхронизация в Java возможна с помощью ключевых слов Java synchronized и volatile, а также с помощью блокировок.
  • В Java не может быть synchronized переменной. Использование ключевого слова synchronized с переменной недопустимо и приведет к ошибке компиляции. Вместо использования synchronized переменной в Java вы можете использовать volatile переменную, которая будет указывать потокам JVM читать значение volatile переменной из основной памяти и не кэшировать его локально.
  • Если переменная не используется совместно несколькими потоками, тогда нет необходимости использовать ключевое слово volatile.

Пример использования volatile:

public class Singleton {

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

Мы лениво создаем экземпляр, когда приходит первый запрос.

Если мы не сделаем переменную instance volatile, тогда поток, который создает экземпляр Singleton, не сможет взаимодействовать с другим потоком. Таким образом, если поток A создает экземпляр Singleton и сразу после создания ЦП повреждается и т. д., все другие потоки не смогут увидеть значение instance как ненулевое, и они будут полагать, что ему по-прежнему присвоено значение null.

Почему это происходит? Поскольку потоки чтения не выполняют никаких блокировок и пока поток записи не выйдет из синхронизированного блока, память не будет синхронизироваться, и значение instance не будет обновляться в основной памяти. С ключевым словом volatile в Java это обрабатывается самой Java, и такие обновления будут видны всем потокам чтения.

Заключение: ключевое слово volatile используется для передачи содержимого памяти между потоками.

Пример использования без volatile (не потоко-безопасное):

public class Singleton { 

    private static Singleton instance;

    public static Singleton getInstance() {   
        if(instance == null) {  
            synchronized(Singleton.class){  
                if(instance == null) 
                    instance = new Singleton(); 
            }
        } 
        return instance;  
    }   
}

Приведенный выше код не является потокобезопасным. Хотя он еще раз проверяет значение экземпляра в синхронизированном блоке (по соображениям производительности), JIT-компилятор может изменить байт-код таким образом, чтобы ссылка на экземпляр была установлена до того, как конструктор завершил свое выполнение. Это означает, что метод getInstance() возвращает объект, который, возможно, не был полностью инициализирован. Чтобы сделать код потокобезопасным, можно использовать ключевое слово volatile, начиная с Java 5 для переменной экземпляра. Переменные, помеченные как volatile, становятся видимыми для других потоков только после того, как конструктор объекта полностью завершит свое выполнение.

Использование volatile в Java:

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

  • Когда список обновляется, счетчик увеличивается.
  • Когда создается Iterator, текущее значение счетчика внедряется в объект Iterator.
  • Когда выполняется операция Iterator, метод сравнивает два значения счетчика и выдает исключение ConcurrentModificationException, если они различны.

Реализация отказоустойчивых итераторов обычно легка. Обычно они полагаются на свойства структур данных конкретной реализации списка.

volatile очень полезен для остановки потоков.

Не то чтобы вам следовало писать свои собственные потоки, в Java 1.6 есть много хороших пулов потоков. Но если вы уверены, что вам нужен поток, вам нужно знать, как его остановить.

public class MyThread extends Thread {

    private volatile boolean close = false;

    public void run() {
        while(!close) {
            // выполняем работу
        }
    }
    public void close() {
        close = true;
        // прерываем если необходимо
    }
}

В приведенном выше примере кода поток, читающий close в цикле while, отличается от того, который вызывает close(). Без volatile поток, выполняющий цикл, может никогда не увидеть изменения для close.

Обратите внимание, что нет необходимости в синхронизации.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки