Понять synchronized
Есть класс:
public class IMF {
public static String imf;
public static String getFund() {
synchronized (IMF.class) {
imf = Math.random() * 1000 + " Это поток - " + Thread.currentThread().getName();
return imf;
}
}
}
Есть тест:
public static void main(String[] args) throws InterruptedException {
Runnable r = () -> IMF.getFund();
Thread thread = new Thread(r);
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
Thread thread3 = new Thread(r);
thread.start();
//thread.join();
System.out.println(IMF.imf);
thread1.start();
//thread1.join();
System.out.println(IMF.imf);
thread2.start();
thread2.join();
System.out.println(IMF.imf);
thread3.start();
thread3.join();
System.out.println(IMF.imf);
}
}
Вывод в консоль:
null
null
819.9350876976558 Это поток - Thread-1
797.1414507666868 Это поток - Thread-3
Если я ставлю ключевое слово synchronized на инициализацию imf (для блока или метода, разницы нет), то ничего не происходит. Потоки все равно ведут себя хаотично и если не вызвать join().
Вопрос: Почему так и как правильно использовать синхронизацию?
Ответы (2 шт):
Они и не будут соблюдать порядок. Директива synchronized говорит о том, что данную функцию может исполнять только 1 поток одновременно.
Представьте что функция с директивой - это примерочная в магазине, человек (поток) зашел туда, закрылся и делает то что ему надо, остальные люди (потоки) ждут, пока тот завершит свои дела, откроет шторку и выйдет, после чего заходит следующий и так по-очереди, а вот саму очередь шторка не контролирует.
thread.start(); // запустили поток Thread-0
System.out.println(IMF.imf); // Прочитали переменную.
// Первый поток не успел ещё ничего записать, поэтому null.
// Этот null закэшировался в кэше процессора, в котором исполняется основной поток
thread1.start(); // запустили поток Thread-1
System.out.println(IMF.imf); // прочитали переменную из кэша процессора (==null),
// потому что у основного потока нет синхронизации
thread2.start(); // запустили поток Thread-2
thread2.join(); // дождались завершения thread2
// join() неявно вызывает синхронизацию кэшей, поэтому старое значение (null) сбросилось
System.out.println(IMF.imf); // Прочитали переменную.
// thread1 и thread2 скорее всего оба ждали, пока thread освободит lock,
// поэтому порядок, в котором отработали thread1 и thread2 не определён
// в твоём случае thread2 отработал раньше thread1
// поэтому ты получили значение от thread1 ("Это поток - Thread-1")
thread3.start(); // запустили поток Thread-3
thread3.join(); // дождались завершения thread3 и опять сбросили кэш
System.out.println(IMF.imf); // прочитали значение переменной
Синхронизация у тебя только на одноврменную запись в переменную. На чтение синхронизации нет, порядок выполнения потоков тоже не определён, поэтому ты видишь то, что видишь. В разных запусках можно получить разую печать на консоль.