Pull to refresh

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-ов нет. Поэтому постановка вопроса "Каков результат" кажется не совсем корректна. Было бы правильнее спрашивать что невозможно

Sign up to leave a comment.