Comments 5
Забавным штукам, однако, учат в Учебном центре IBS:
Дано:
pubic class Logger implements Runnable {
String msg;
public Logger(String msg) {
this.msg = msg;
}
@Override
public void run() {
System.out.print(msg);
}
}
String msg
не объявлен ни как "final", ни как "volatile", но явно ожидается, что во время выполнения run()
в другом потоке из msg будет прочитана именно строка, присвоенная в конструкторе - вариантов ответа с выводом "null" не предусмотрено.
Была отличная статья на эту тему на Хабре: Глубокое погружение в Java Memory Model.
Да, действительно, вполне уместное замечание. Дело в том, что мы следуем каноническому подходу, который принят у Oracle и к которому все давно привыкли (кстати, в декабре прошлого года Oracle сертифицировал миллионного разработчика). Сертификационный экзамен традиционно не предполагает, в частности, каких-либо сценариев с компиляторной оптимизаций, поэтому, к примеру, ключевое слово volatile вообще не вынесено на тестирование. Точно так же экзамен не углубляется в специфику Java Memory Model.
Набор предложенных вариантов ответа ограничивает набор сценариев, в которых теоретически способен работать код в вопросах.
Здесь нет логики синхронизации потоков, да и не нужно здесь ничего добавлять. Создание объектов и запуск в отдельных потоках run выполняется одним потоком. Не нужно использовать volatile.
Компайлер может попросить закэшить что-то, если это что-то очень часто и много пишется с одного потока. При этом нет вероятного доступа на чтение из других потоков. За всю свою многолетнюю (больше 20 лет) практику от платёжных и до высоконагруженных систем, я ни разу не воспользовался этим словом.
За повсеместное втыкание final, надо бить по рукам. Что final, что модификаторы видимости - это для библиотек больше. Люди, имеющие доступ к этим модификатором удалят их, если им действительно нужно будет что-то сделать public или перереференсировать.
Надо нанимать людей, которые понимают зачем изменчивость, зачем куча и стек, сильные и слабые стороны ООП и ФП и т.д.
Создание объектов и запуск в отдельных потоках run выполняется одним потоком.
А исполнение тела run()
в другом, и именно в этом теле идёт доступ к записанной в другом потоке переменной.
Компайлер может попросить закэшить что-то, если это что-то очень часто и много пишется с одного потока.
А может не попросить, а может и не может. Это никем не гарантируется. А вот то, что изменение (запись строки) может быть не замечено явно описано, причём не в какой-то отдельной спеке, а прямо таки в спецификации языка.
За всю свою многолетнюю (больше 20 лет) практику от платёжных и до высоконагруженных систем, я ни разу не воспользовался этим словом.
Апелляция к опыту это, конечно, прикольно, но она ничего не говорит, кроме того, что лично вы на такое не наткнулись (а, может, просто не заметили). Вот вам конкретный контрпример из Lucene LUCENE-8780.
За повсеместное втыкание final, надо бить по рукам. Что final, что модификаторы видимости - это для библиотек больше. Люди, имеющие доступ к этим модификатором удалят их, если им действительно нужно будет что-то сделать public или перереференсировать.
Плохие разработчики мешают стрелять по ногам. Эти вещи придумали не просто так, а для решения конкретных проблем. Если что-то не должно изменяться, то мне, как разработчику, гораздо удобнее явно иметь признак этого (final
на поле) и отсюда какую-то гарантию того, что никто мне поле не поменяет [1]. Как типовой пример, что final
-- это хорошо, вспомните, как устроены строки и как они полагаются на свою иммутабельность.
Причём эти вещи позволяют (и тут мы уже переходим к реальным системам) рантайму быть эффективнее, например компилятору (JITу) с чистой совестью решать, что он может кэшировать, а что нет, что он может держать локально, а что должен закоммитить в память.
Надо нанимать людей, которые понимают зачем изменчивость, зачем куча и стек, сильные и слабые стороны ООП и ФП и т.д.
А неизменяемость это не какая-то уникальная особенность ФП. Ей есть место много где, включая ООП. Да и Java это не чисто-ООП язык, а мультипарадигмальный. И если посмотреть на вектор развития языка, то многие нововведений предпочитают иммутабельность (records, scoped values, frozen arrays).
[1]: На самом деле, есть штуки три способов в обход final
записать поле, но они больно экзотические, плюс могут вести к деоптимизациям, плюс с ними постепенно пытаются бороться в OpenJDK.
На самом деле для видимости ни final, ни volatile не нужны, потому что:
Memory consistency effects: Actions in a thread prior to
submitting a Runnable
object to an Executor
happen-before
its execution begins, perhaps in another thread.
Поэтому null невозможен в принципе. Другое дело, что возможна любая перестановка указанных трех строчек, потому что никаких гарантий относительно того как OS запланирует исполнение thread-ов нет. Поэтому постановка вопроса "Каков результат" кажется не совсем корректна. Было бы правильнее спрашивать что невозможно
Вопрос на сертификационном экзамене: применение Threads и Executors