Ерес на последното поле на Java: разсъждавам ли правилно?

Наскоро започнах да уча C# и се насочих направо към модела на паметта. C# и Java имат подобни (макар и може би не идентични) гаранции за безопасност на нишките по отношение на четене и запис на volatile полета. Но за разлика от записите в final полета в Java, записите в readonly полета в C# не предоставят конкретна гаранция за безопасност на нишката. Мислейки за това как работи безопасността на нишките в C#, ме накара да се съмнявам дали има някакво реално предимство полетата final да се държат по начина, по който се държат в Java.

Научих за предполагаемата важност на final преди три години. Зададох този въпрос и получих подробен отговор, който приех. Но сега мисля, че е грешно или поне без значение. Все още смятам, че полетата трябва да бъдат final, когато е възможно, просто не поради причините, за които се смята, че обикновено се смята.

Стойността на поле final е гарантирано видима за всяка друга нишка, след като конструкторът се върне. Но препратката към самия обект трябва да бъде публикувана по безопасен начин. Ако препратката е публикувана безопасно, тогава гаранцията за видимост на final става излишна.

Обмислих възможността това да има нещо общо с public static полета. Но логично зареждането на клас трябва да синхронизира инициализацията на класа. Синхронизирането прави безопасността на нишката на final излишна.

Така че излагам еретичната идея, че единствената истинска стойност на final е да направи неизменността самодокументираща се и самоналагаща се. На практика частните неокончателни полета (и по-специално елементите на масив) са напълно безопасни за нишки, стига да не бъдат модифицирани след връщането на конструктора.

Греша ли?

Редактиране: Перифразирайки раздел 3.5 от Java Concurrency на практика,

Две неща могат да се объркат с неправилно публикувани обекти. Други нишки могат да видят остаряла стойност за препратката и по този начин да видят нулева препратка или друга по-стара стойност, въпреки че е зададена стойност. Но много по-лошо е, че други нишки могат да видят актуална стойност за референтната стойност, но остарели стойности за състоянието на обекта.

Разбирам как полетата final решават втория проблем, но не и първия проблем. Най-високо гласуваният отговор досега твърди, че първият проблем не е проблем.

Редактиране 2: Този въпрос възниква от объркване на терминологията.

Подобно на задаващия подобен въпрос, винаги съм разбирал термина „безопасно публикуване“ като значение, че вътрешното състояние на обекта и препратката към самия обект са гарантирано видими за други нишки. В полза на това определение Effective Java цитира Goetz06, 3.5.3 при дефинирането на „безопасна публикация“ като (курсивът е добавен)

Прехвърляне на такава препратка към обект от една нишка към друга

Също така в полза на това определение, имайте предвид, че разделът на Java Concurrency in Practice, перифразиран по-горе, се отнася до потенциално остарели препратки като „неправилно публикувани“.

Както и да го наречете, не мислех, че небезопасното публикуване на препратка към неизменен обект може да бъде полезно. Но според този отговор може. (Примерът, даден там, е примитивна стойност, но същият принцип може да се прилага за референтни стойности.)


person Kevin Krumwiede    schedule 29.08.2017    source източник
comment
частните неокончателни полета (и по-специално елементите на масива) са напълно безопасни за нишки, стига да не бъдат модифицирани след връщането на конструктора - това е погрешно. Ако друга нишка получи препратка към вашия обект без правилна синхронизация, тя може да види тези полета или стойности на масива като неинициализирани, дори ако са били инициализирани в конструктора.   -  person Erwin Bolwidt    schedule 29.08.2017
comment
@ErwinBolwidt Да, но OP изглежда е с впечатлението, че препратките никога не трябва да се споделят без синхронизация.   -  person shmosel    schedule 29.08.2017
comment
@shmosel Мисля, че си прав. Също така изглежда, че OP смята, че синхронизирането е абсолютно нещо, но правилна JMM връзка между нишки, използващи синхронизация, се постига само чрез синхронизиране на същия монитор (също така, правилата, свързани с непостоянни данни, се прилагат само към същата променлива променлива и т.н.) и зареждащ клас, извършващ статична инициализация, няма да принуди всички други нишки да се синхронизират на неговия монитор по някакъв начин. (между другото имам предвид правилна синхронизация според JMM терминологията, която може да бъде постигната по различни начини)   -  person Erwin Bolwidt    schedule 29.08.2017
comment
@ErwinBolwidt Мисля, че OP е правилен по въпроса за инициализацията на статично поле. Вижте например stackoverflow.com/q/878577/1553851.   -  person shmosel    schedule 29.08.2017
comment
@ErwinBolwidt Искам да кажа, че това изречение е вярно, ако и само ако препратките са публикувани безопасно.   -  person Kevin Krumwiede    schedule 29.08.2017
comment
Най-високо гласуваният отговор досега твърди, че първият проблем не е проблем. Не съвсем. Аргументът е, че това е отделно съображение и не трябва да се смесва с втория, много по-лош проблем.   -  person shmosel    schedule 29.08.2017
comment
Имайте предвид, че задаващият въпроса, който сте свързали (страхотна находка между другото!) оттегли тълкуването си.   -  person shmosel    schedule 29.08.2017
comment
@shmosel Видях това. Все още смятам, че терминологията е подвеждаща, тъй като потенциално неуспешното публикуване на стойност може да се счита за безопасно само при много специфични обстоятелства. Също така, надеждността на това вероятно зависи от архитектурата. Наскоро научих, че x86/x64 налага по-силни гаранции за видимост на по-слабите Java, но ARM (например) не. Така че почти безопасното публикуване на неизменни обекти може да работи на компютър и да се провали на Android.   -  person Kevin Krumwiede    schedule 29.08.2017


Отговори (2)


Но препратката към самия обект трябва да бъде публикувана по безопасен начин. Ако препратката е публикувана безопасно, тогава гаранцията за видимост на final става излишна.

Първото изречение е грешно; следователно второто е без значение. final може да е излишен при наличието на други техники за безопасно публикуване, като синхронизация или volatile. Но смисълът на неизменните обекти е, че те са по своята същност безопасни за нишки, което означава, че ще се виждат в последователно състояние, независимо от това как е публикувана препратката. Така че не се нуждаете от тези други техники на първо място, поне що се отнася до безопасното публикуване.

РЕДАКТИРАНЕ: OP правилно посочва, че има известна неяснота около термина "безопасно публикуване". В този контекст имам предвид последователността на вътрешното състояние на обекта. Въпросът с видимостта, тъй като засяга препратката, според мен е основателна, но отделна загриженост.

person shmosel    schedule 29.08.2017
comment
Това, което имам предвид е, че докато самите обекти може да са безопасни за нишки, препратките към тях не са автоматично безопасни. Например, не би било безопасно нишките да комуникират чрез четене и писане на неокончателно, енергонезависимо референтно поле, независимо от присъщата безопасност на нишката на типа на полето. Ако нишката за четене види записаната референтна стойност, тогава да, обектът, към който се отнася, ще се разглежда като напълно инициализиран. Но може да не види референтната стойност на първо място. - person Kevin Krumwiede; 29.08.2017
comment
Безопасността на нишките може да означава много различни неща. По-спокойна дефиниция може да позволи забавена видимост, но изисква последователност на паметта. Всичко зависи от вашите специфични изисквания. - person shmosel; 29.08.2017
comment
Не знам за забавена видимост. AFAIK, стойностите са или гарантирано видими от връзката случва-преди между писане и четене, или тяхната видимост е напълно неопределена. - person Kevin Krumwiede; 29.08.2017
comment
За да поясня, не ми е известно за някаква евентуална гаранция за последователност в JMM. Може да е характеристика на конкретно изпълнение или архитектура. - person Kevin Krumwiede; 29.08.2017
comment
И аз не знам за такава гаранция, но доколкото знам, на практика работи по принцип по този начин. Но каквато и терминология да предпочитате, моето мнение е, че видимостта може да не е от съществено значение за коректността във всички случаи. - person shmosel; 29.08.2017
comment
@KevinKrumwiede За практически пример, помислете за двойно проверено заключване. Обикновено казваме, че полето трябва да е volatile или има възможност да видите ненулева променлива с частично конструиран обект. С неизменни обекти това е невъзможно. - person shmosel; 29.08.2017
comment
Не считам двойно провереното заключване за практически пример за каквото и да било. Преди Java 1.5 беше известно, че е повреден. След Java 1.5 цената на неоспоримо заключване беше толкова ниска, че всяко предимство в производителността на двойно провереното заключване стана изключително съмнително. Както и да е, защо бихте използвали двойно проверено заключване с неизменен обект? - person Kevin Krumwiede; 29.08.2017
comment
Очевидно говоря за след 1.5. Що се отнася до това, че е безспорен, това зависи от количеството едновременни четения. Така или иначе, моето намерение беше да осигуря действителен сценарий, при който коректността не е възпрепятствана от проблеми с видимостта. Всъщност не беше важното дали този сценарий би бил директно полезен за вас. Що се отнася до това защо някой би го използвал, същото е както винаги: за лениво инициализиране на някаква стойност по безопасен за нишки начин. Не виждам защо неизменността на стойността би променила нещо. - person shmosel; 29.08.2017
comment
Не виждам защо неизменността на стойността би променила нещо. - Точно защо не разбирам значението на двойно провереното заключване за моя въпрос. - person Kevin Krumwiede; 29.08.2017
comment
Променя всичко по отношение на безопасността на нишките, както вече обясних. Не разбирам защо смятате, че DCL е по-малко полезен за неизменни обекти. - person shmosel; 29.08.2017
comment
Не, мисля, че е еднакво (макар и съмнително) полезно както за неизменни, така и за ефективно неизменни обекти, точно защото включва използването на volatile за осигуряване на видимостта на препратката. Под безопасно публикуване имам предвид видимо последователно състояние и видимост на препратката. Сега разбирам, че вие ​​и другите приемате, че това означава само първото. - person Kevin Krumwiede; 29.08.2017
comment
Исках да кажа, че няма да имате нужда от volatile в този случай. Видимостта не е проблем, тъй като се наблюдава, че променливата не е нула. А последователността се осигурява от неговата неизменност. И да, твоето разбиране на моето разбиране е точно. :) - person shmosel; 29.08.2017
comment
@shmosel volatile не е достатъчно за безопасно публикуване - тъй като няма да въведе необходимите бариери за паметта. Всъщност, дори ако можете да заобиколите това (а бихте могли), всеки един volatile ще въведе StoreLoad, което е най-скъпата операция на x86, докато final с StoreStore|LoadStore са безплатни. Бих премахнал тази част volatile - person Eugene; 30.08.2017
comment
@Eugene Не съм съвсем сигурен на какво отговаряш. - person shmosel; 30.08.2017
comment
@shmosel лошото ми... final may be redundant in the presence of other safe publication techniques, like synchronization or volatile. volatile не трябва да бъде в това изречение. - person Eugene; 30.08.2017
comment
@Eugene Мисля, че не разбирате отговора ми, а вероятно и въпроса. Дискусията е за правене на препратка volatile или синхронизиране на достъпа до същата, а не директно заместване на final с volatile. - person shmosel; 30.08.2017
comment
@shmosel останах с впечатлението, че говорите за безопасно публикуване и как това се прави чрез final; Съжалявам, ако не съм го разбрал. - person Eugene; 30.08.2017
comment
@Eugene Мисълта ми беше, че използването на final полета в полето (т.е. правенето му правилно неизменно) прави ненужно налагането на безопасно публикуване на референтно ниво. Надявам се, че можем да се съгласим по този въпрос. - person shmosel; 30.08.2017

Прочетох въпроса ви няколко пъти и все още имам някои проблеми с разбирането му. Просто ще се опитам да допълня другия отговор - който всъщност е правилен. final в Java е всичко свързано с пренареждане (или се случва преди, както го наричате).

Първо, това е гарантирано от JLS, вижте Семантика на окончателното поле. В примера можете да видите, че едно крайно поле гарантирано ще бъде видяно правилно от други нишки, докато другото не е. JLS е правилен, но при текущата реализация би било достатъчно едно поле да бъде final - прочетете по-нататък.

Всеки един запис в крайно поле вътре в конструктор е последван от две memory barriers - StoreStore и LoadStore (по този начин името се случва-преди); защото съхраняването на крайно поле ще се случи преди четене на същото поле - гарантирано чрез бариери на паметта.

Но текущата реализация не прави това - което означава, че бариерите на паметта не се случват след всяко едно записване във финал - те се случват в края на конструктора, вижте това. Ще видите този ред, който е важен:

 _exits.insert_mem_bar(Op_MemBarRelease, alloc_with_final());

Op_MemBarRelease е действителната LoadStore|StoreStore бариера, поради което при текущата реализация е достатъчно да имате едно поле, което да е окончателно, за всички други също да бъдат безопасно публикувани. Но, разбира се, правете това на свой собствен риск.

volatile в този контекст няма да направи достатъчно публикация - тъй като просто няма да въведе необходимите бариери, можете да прочетете малко повече тук.

Имайте предвид, че публикуването на препратки не е проблем, просто защото JLS казва: Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values..

person Eugene    schedule 30.08.2017
comment
Основното нещо, което имах предвид, беше, че бариерата на паметта, за която говорите, гарантира само, че стойностите на полетата на обекта са видими за други нишки. Това не гарантира, че стойността на препратка към обекта е видима за други нишки. (Препратките, които са атомарни, също нямат нищо общо с тяхната видимост.) Бях прав за това какво се случва, ако препратката не е безопасно публикувана. Грешах, че несигурно публикуваните препратки винаги са грешни. - person Kevin Krumwiede; 30.08.2017
comment
@KevinKrumwiede ах! сега има смисъл - вие говорите за две различни неща Safe Initialization и Safe Publication тук. Наистина за безопасно публикуване ще трябва препратката да бъде volatile например. Тук има много повече: shipilev.net/blog/2014/safe-public- строителство - person Eugene; 30.08.2017
comment
@KevinKrumwiede и между другото, това все още би било за бариери в паметта - тъй като volatile ще въведе бариерите в паметта, необходими за действията, извършени преди летливо записване, за да бъдат видими след летливото четене; като по този начин препратката volatile би била достатъчна. - person Eugene; 30.08.2017
comment
вярно Част от объркването ми беше, че някои източници очевидно използват термина безопасно публикуване, за да означават безопасно инициализиране, а не това, което аз смятам за безопасно публикуване. - person Kevin Krumwiede; 30.08.2017
comment
@KevinKrumwiede добре, в такъв случай трябва да промените заглавието на въпроса, за да отразите това - бихте могли да помогнете на много хора в бъдеще да търсят точно тези неща. Благодаря ти - person Eugene; 31.08.2017