Я считаю, что примеры, используемые для объяснения синхронизации и волатильности, надуманы и трудны для понимания цели. Вот мои предпочтительные примеры:
Синхронное:
private Value value;
public void setValue(Value v) {
value = v;
}
public void doSomething() {
if(value != null) {
doFirstThing();
int val = value.getInt(); // Will throw NullPointerException if another
// thread calls setValue(null);
doSecondThing(val);
}
}
Приведенный выше код совершенно корректен, если он выполняется в однопоточной среде. Однако даже с двумя потоками существует вероятность того, что value
будет изменено между проверкой и ее использованием. Это потому, что метод doSomething()
является не атомарным .
Для решения этой проблемы используйте синхронизацию:
private Value value;
private Object lock = new Object();
public void setValue(Value v) {
synchronized(lock) {
value = v;
}
}
public void doSomething() {
synchronized(lock) { // Prevents setValue being called by another thread.
if(value != null) {
doFirstThing();
int val = value.getInt(); // Cannot throw NullPointerException.
doSecondThing(val);
}
}
}
Volatile:
private boolean running = true;
// Called by Thread 1.
public void run() {
while(running) {
doSomething();
}
}
// Called by Thread 2.
public void stop() {
running = false;
}
Для объяснения этого требуется знание модели памяти Java. Стоит прочитать более подробно, но короткая версия для этого примера состоит в том, что потоки имеют свои собственные копии переменных, которые синхронизируются только с основной памятью синхронизированного блока и при достижении энергозависимой переменной. Компилятору Java (в частности, JIT) разрешено оптимизировать код так:
public void run() {
while(true) { // Will never end
doSomething();
}
}
Чтобы предотвратить эту оптимизацию, вы можете установить переменную как volatile, которая заставляет поток обращаться к основной памяти каждый раз, когда он читает переменную. Обратите внимание, что в этом нет необходимости, если вы используете синхронизированные операторы, поскольку оба ключевых слова вызывают синхронизацию с основной памятью.
Я не отвечал на ваши вопросы напрямую, как это сделал Фрэнсис. Я надеюсь, что эти примеры могут дать вам представление о концепциях лучше, чем примеры, которые вы видели в руководстве по Oracle.