Про службове слово volatile джава програмісти згадують в більшості перед
співбесідами або на них. І мало хто до кінця розбирається що воно таке і з чим
його їдять. Причина для цього досить проста, щоб знати коли його
використовувати необхідно мати досить непогані знання в галузях cpu регістру, кешування. Інша
причина полягає в тому, що досить важко продемонструвати наслідки не
використання цоьго слова. Саме з цим ми постараємось розібратись з-за допомогою
маленької задачки.
Отже до задачки, спочатку не запускаючи програму подумайте
що станеться з таким кодом
1. public class ConcurrencyFun implements Runnable
2. {
3. private String str;
4. void setStr(String str)
5. {
6. this.str = str;
7. }
8. public void run()
9. {
10. while (str == null);
11. System.out.println(str);
12. }
13. public static void main(String[] args) throws Exception
14. {
15. ConcurrencyFun fun = new ConcurrencyFun();
16. new Thread(fun).start();
17. Thread.sleep(1000);
18. fun.setStr("Hello world!!");
19. }
20. }
Більшість мабуть скажуть що код буде чекати 1 секунду і
виводити повідомлення "Hello
World!". Новостворений (поороджений) потік чекає поки str не null
і виводить його. Основний потік запускає породжений(?) потік, чекає 1
сек і вставляє в str значення
"Hello World!". Виглядає
все просто? Чи не так?
Тепер спробуйте запустити цей код. На моїй машині програма виконується
приблизно вічно :). Чому
так?
Причина в тому, що JVM можестворювати свою власну копію на референс до str для кожного
новоствореного потоку який її використовує. Це може бути в багатьох формах:
може бути так що референс загружається в регістр і звідти постійно зчитується.
Це мабуть те що і відбувається в нашому випадку. Може бути що референс загрузився
в CPU кеш і ніколи не
експайриться, навіть після обновлення (апдейту). Або також можливо що JVM створить копію на
референс в потоках для більш ефективного використання доступу до памяті. Не
залежно від того чи ви зрозуміли те що було згори, суть полягає в тому що зміни
в полі str не
обовязково будуть видимі всіма потоками які до цього поля доступаються, в
нашому випадку вони ніколи не будуть видимі.
Саме тут і потрібне volatile. Ключове слово volatile повідомляє JVM, що будь-які записи у це поле повинні бути видимими для всіх потоків.
Це означає що скомпільований код не може зчитувати значення змінної з регістру
і використовувати його багато разів, а повинен кожного разу зчитувати його з
памяті. Аналогічно з CPU кешом
і з копіюванням JVM локальної
копії в потоки stack frame.
Отже наш код буде
мати вигляд:
1. public class ConcurrencyFun implements Runnable
2. {
3. private volatile String str;
4. void setStr(String str)
5. {
6. this.str = str;
7. }
8. public void run()
9. {
10. while (str == null);
11. System.out.println(str);
12. }
13. public static void main(String[] args) throws Exception
14. {
15. ConcurrencyFun fun = new ConcurrencyFun();
16. new Thread(fun).start();
17. Thread.sleep(1000);
18. fun.setStr("Hello world!!");
19. }
20. }
Як результат програма буде корректно працювати і друкувати
привіт світу саме тоді коли і потрібно.
Посилання на оригінал статті:
http://jazzy.id.au/default/2009/04/24/java_concurrency_and_volatile.html
ПС: Якщо ви прочитали (передивились) оригіналі статті, то помітили, що там згадуються тільки Linux s Solaris OS, але я пробував приклади на своєму i5, Win7 і результат такий самий.
ПС2 Чекайте звіте з JavaDay 2012
>> досить важко продемонструвати наслідки не використання цоьго слова
ВідповістиВидалитинаприклад, потрібна можливість прервати потік - іншого способу, ніж флаг з волатайлом я і не знаю
Є ще варіант хорошого прикладу реалізація Singletone патерну "Double Checked Locking & volatile"
ВідповістиВидалитиpublic class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
Singleton localInstance = instance;
if (localInstance == null) {
synchronized (Singleton.class) {
localInstance = instance;
if (localInstance == null) {
instance = localInstance = new Singleton();
}
}
}
return localInstance;
}
}
p.s.
детальніше можна почитати на http://habrahabr.ru/post/129494/