Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

Проблема периодизации русской литературы ХХ века. Краткая характеристика второй половины ХХ века

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






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




 

Определение переменной с ключевым словом volatile («изменчивый») означает, что значение переменной будет изменяться разными потоками. Проще говоря, когда два и более потока работают с одной и той же переменной, то для того, чтобы потоки видели актуальные данные переменной, она должна быть объявлена как volatile.

Декларирование переменной как volatile гарантирует:

  • любой из потоков будет видеть самые последние данные переменной;
  • обращение к переменной будет происходить в порядке определённом на стадии компиляции;
  • несколько потоков не получат одновременный доступ к переменной

Чтобы полностью понять, что значит volatile, во-первых, нужно понять, как

потоки оперируют с обычными, не-volatile, переменными. В целях повышения эффективности работы, спецификация языка Java позволяет JVM сохранять локальную копию переменной в каждом потоке, который ссылается на нее. Можно считать эти «внутрипоточные» копии переменных похожими на кэш, помогающий избежать проверки главной памяти каждый раз, когда требуется доступ к значению переменной. Теперь представьте, что произойдёт в следующем случае: запустятся два потока, и первый прочитает переменную А как 5, тогда как второй – как 10. Если переменная А изменились от 5 до 10, то первый поток не будет знать об изменении, так что будет иметь неправильное значение А. Однако если переменная А будет помечена как volatile, то в любое время, когда поток обращается к её значению, он будет получать копию А и считывать её текущее значение.

Алгоритм работы с volatile переменной очень схож с принципом synchronized. За исключением того, что при использовании synchronized работа идет с методами и блоками кода, а при использовании volatile работа идет только с одной переменной.

Когда поток обращается к volatile переменной, то происходят следующие действия JVM:

  1. Устанавливается глобальный lock на переменную.
  2. Читается значение переменной из основной памяти и записывается в локальную переменную потока.
  3. Снимается глобальный lock на переменную.

 

Рассмотрим интересный пример:

public class Checker {

private volatile int counter = 0;

public static void main(String[] args)

throws InterruptedException {

Checker obj = new Checker();

Thread t0 = new Thread(new CounterThread(obj));

Thread t1 = new Thread(new CounterThread(obj));

t0.start();

t1.start();

t0.join();

t1.join();

System.out.println(obj.counter);

}

public void increase() {

counter++;

}

}

 

class CounterThread implements Runnable {

private Checker checker = null;

public CounterThread(Checker checker) {

this.checker = checker;

}

public void run() {

for (int i = 0; i <= 1000000; i++) {

checker.increase();

}

}

}

 

Два созданных потока t1 и t2 получают доступ к целому полю counter одного объекта obj. Поле помечено ключевым словом volatile и увеличивается потоками на единицу в цикле, который выполняется 1000000 раз. Таким образом, ожидается, что результат выполнения программы будет 2000000. Однако если запустить программу, то вы сможете убедиться, что результат работы будет каждый раз разный, но практически всегда меньше 2000000. При первом запуске на моем компьютере было получено: 1051843.

В чем проблема? Ведь мы используем volatile-переменную? Дело в том, что volatile НЕ гарантирует атомарность над переменной. Получается, если надо увеличить значение int переменной на единицу (++), то произойдет два, а не одно обращение к переменной (counter++ выполняется как counter=counter+1). К примеру, при той же операции ++, первый поток считывает volatile переменную из основной памяти (текущее значение 1) и обновляет свою локальную переменную (lock -> read -> unlock), затем после разблокировки переменной – второй поток считывает значение переменной (текущее значение 1). Далее, первый поток прибавляет единицу к значению своей локальной переменной (текущее значение 2) и записывает ее в основную память и затем второй поток прибавляет единицу к значению своей локальной переменной (текущее значение 2) и записывает ее в основную память. Таким образом, в основной памяти будет величина равная 2, а не 3 как задумывалось. Чтобы избежать таких ошибок, необходимо использовать synchronized или готовые атомарные классы пакета java.util.concurrent.atomic.

Если в примере сделать метод синхронизированным

public synchronized void increase() {

counter++; }

то получим верный результат 2000000.

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






Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных