Synchronizace, JMM a další špeky — díl první
Nejsem si úplně jistý, kolik z Vás zaznamenalo všechny změny kolem problematiky konkurenčního zpracování, které představila Java 5. Byla to skutečně Java 5, která představila některé nové vlastnosti, které umožňují efektivní práci multithread aplikací. Pokud jste novinky v této oblasti nesledovali, pak Vám tento článek poslouží jako takové malé intro.
Java Memory Model
Java Memory Model (JMM) definuje vztah mezi proměnnými programu a tím jak jsou uloženy a získávány z paměti počítače. JMM je součástí Java Language Specification a po pravdě není potřeba, aby jej vývojáři znali. Nicméně z jeho sémantiky vyplývá několik věcí, které si musí každý vývojář uvědomit pokud pracuje s multithread (vícevláknovou) aplikací.
Říkám několik věcí, ale ve skutečnosti jsou to dva podstatné fakty a to viditelnost proměnných mezi vlákny a přeskupení pořadí zápisu a čtení proměnných. Každá proměnná leží v paměti avšak během práce s ní může docházet k různým výkonovým optimalizacím, například se proměnná uloží do lokální cache či CPU registru. Tyto optimalizace může dělat HotSpot (JVM runtime compiler) a nebo to může být hardwarová optimalizace. JMM určuje sémantiku, která definuje jak se mohou tyto optimalizace promítnout vzhledem k práci s proměnnými v Jave a to především z hlediska threadů.
Mnoho vývojářů vidí v rámci řešení konkurenčních problémů pouze jeden aspekt klíčového slova synchronized a to atomicitu v podobě vzájemného vyloučení vláken nad kritickou sekcí. Jenže se synchronized je spojena viditelnost proměnných mezi vlákny a přeskupení pořadí jejich čtení zápisu, to jest přímá souvislost s JMM.
class Reordering {
int x = 0, y = 0;
public void writer() {
x = 1;
y = 2;
}public void reader() {
int r1 = y;
int r2 = x;
}
}Představme si dvě vlákna, která pracují s výše uvedeným kouskem kódu a řekněme, že jedno vlákno nám provádí metoduwriter. Vzápětí přijde druhé vlákno, které začne provádět metodu reader. Předpoklad, že po provedeníint r1 = y;(r1 == 2) automaticky platí, žer2 == 1není správný. První vlákno mohlo resp. kompiler mohl přehodit pořadí těch dvou příkazů, což může udělat v případě, že to neovlivní běh samotného vlákna. Druhou možností je, že vlákno vykonávající metodureadervidí resp. pracuje s nacacheovanými hodnotamix,y.
Proto, aby nedošlo k přehození těchto příkazů a zároveň k zaručení viditelnosti aktuálních hodnot přes všechny vlákna slouží také klíčové slovosynchronized.Synchronizedje z pohledu JMM definováno tak, že při získání zámku dojde načtení čerstvých hodnot z hlavní paměti a při uvolnění zámku dojde k jejich zápisu do hlavní paměti. Zároveň v rámci synchornized bloku nemůže dojít k takové optimalizaci, že by paměť zůstala v nekonzistentním stavu. Díkysynchronizedtak vlákna vždy vidí skutečný obraz paměti.
Pokud se ptáte jak s tímto tématem souvisí Java 5, tak s ním souvisí tak, že upravuje některé nepřesnosti a mezery v JMM specifikaci. V předchozí verzi JMM (do verze Javy 1.5) mohlo například dojít k tomu, že vlákna viděli na objekt, který nebyl plně zinicializován viz. double checked locking. Stejně tak mohlo dojít k tomu, že dvě vlákna mohla vidět final proměnnou ve dvou rozdílných stavech.Další změnou v JMM specifikaci je rozšířená sémantika proměnné označené klíčovým slovemvolatile.Volatileo proměnné říká, že thread musí vždy pracovat s proměnnou v hlavní paměti. Starý model ovšem provolatileumožňoval, že mohlo dojít k přeskupení non volatile práce s proměnnou a obyčejnou proměnnou.
class Reordering {
int x = 0
volatile y = 0;
public void writer() {
x = 1;
y = 2;
}public void reader() {
if(y == 2) {
//x ==1
}
}
}Díky úpravě JMM ve verzi 1.5 sémanticevolatileje garantováno, že vlákno vidí i předchozí paměťové změny.Volatilemá totiž po novu stejné chování (viditelnost, reorder) při práci s pamětí jakosynchronized.
Celý nový JMM je pokrytý JSR 133, ale doporučuji si spíše přečíst některý z následujících článků, protože těm porozumí i běžný vývojář.
V dalším článku se podíváme na témata, která už budou blíže reálnému vývoji a to na alternativní způsoby k synchronizaci, které nabízí balík java.util.concurrent a jeho neblokující algoritmy.Související články
Související epizody CZ Podcastu
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi a Filemon o novinkách v Javě 6 a webových frameworcích.
- #1 · Vývojová prostředí v Javě
První díl CZ Podcastu se věnuje vývojovým prostředím pro Javu.