Понять 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 шт):

Автор решения: SemperAnte

Они и не будут соблюдать порядок. Директива synchronized говорит о том, что данную функцию может исполнять только 1 поток одновременно. Представьте что функция с директивой - это примерочная в магазине, человек (поток) зашел туда, закрылся и делает то что ему надо, остальные люди (потоки) ждут, пока тот завершит свои дела, откроет шторку и выйдет, после чего заходит следующий и так по-очереди, а вот саму очередь шторка не контролирует.

→ Ссылка
Автор решения: Alexander Pavlov
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); // прочитали значение переменной

Синхронизация у тебя только на одноврменную запись в переменную. На чтение синхронизации нет, порядок выполнения потоков тоже не определён, поэтому ты видишь то, что видишь. В разных запусках можно получить разую печать на консоль.

→ Ссылка