Double checked locking
Autor: Petr FerschmannPeter Stibrany mne před časem upozornil na zajímavou přednášku o paměťovém modelu Javy: JavaOne 2006 a specifikace Threads and Locks.
Java má (např. oproti C/C++) jasně definované chování mezi více vlákny či procesory napříč všemi platformami. A při programování lze na takto definované chování samozřejmě spoléhat.
V C/C++ bylo velmi oblíbené používání tzv. double checked locking. Cílem tohoto návrhového vzoru je umožnit opožděné vytvoření instance pro singleton, ale zároveň zabránit zbytečné synchronizaci:
Helper helper;
Helper getHelper() {
if (helper == null)
synchronized(this) {
// opětovná kontrola pokud došlo ke změně
if (helper == null) {
helper = new Helper();
}
}
return helper;
}
Naivní chování, kdy při hodnotě null zamknete objekt a pak provedete opětovnou kontrolu v Javě fungovat nebude (přesněji v 99% případů bude, ale stále je zde ono procento), protože díky cache u jednotlivých procesorů se o změně nedozvíte (ani v bloku synchronized). Ono nefungování nemusí být problém pokud nevadí, že může vzniknout několik instancí objektu Helper.
Správně musíte použít klíčové slovo volatile. Tím ale způsobíte vždy nové načtení záznamu z hlavní paměti (tj. bez použití cache).
volatile Helper helper;
Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
return helper;
}
Nejefektivnější a nejjednodušší (využívá pravidla, že statické prvky jsou inicializované při prvním volání metody třídy):
class Helper {
static final Helper helper = new Helper();
public static Helper getHelper() {
return helper;
}
}
Také je zde zajímavá zmínka o efektivnosti použití final. Jeho použití je tedy doporučované nejen kvůli zvýšení čitelnosti kódu, ale také umožnění dalších optimalizací překladače.
Doporučuji vám si prohlédnout obsah obou odkazů.


1. Březen 2008 v 09:49
Mala poznamka, to bude fungovat az od Javy 1.5 diky rozsisreni semantiky volatile o happens-before.
1. Březen 2008 v 13:47
Je pravda, že od Javy 5 (díky aktualizovanému memory modelu) double-checked locking funguje, pokud se použije modifikátor volatile, ale stejně mi to přijde nepěkné.
Ostatně, pokud nevadí, že objektů vznikne víc, synchronizace není potřeba vůbec, a od Javy 6 by z výkonnostního pohledu neměl být problém použít ten vůbec nejjednodušší přístup: synchronizovat celou metodu.
Pěkně je to (a nejen to) popsané v knížce http://www.javaconcurrencyinpractice.com/, doporučuju.
1. Březen 2008 v 18:39
No, diky za info, tuhle dvoji kontrolu pouzivam bezne – je fajn se dozvedet, ze to nestaci :-).
2. Březen 2008 v 15:08
1. volatile je mozne bezpecne pouzit az od Javy 1.5. V nizsich verzich Javy byla chyba v pametovem modulu a nepracovalo se s takovymi fieldmi spravne. Proto je treba v techto verzich Javy zvolit, jestli je mozne pouzit synchronizovanou metodu za cenu vykonu, nebo inicializovat field pri zavadeni tridy do pameti, cimz se straci flexibilita.
2. “Nejefektivnější a nejjednodušší (využívá pravidla, že statické prvky jsou inicializované při prvním volání metody třídy)”
- nejjednoduchsi to je.
- nejefektivnejsi je sportne. Pri zavolani prvni metody tridy a zavedeni tridy do pameti dojde k nacteni vsech statickych elemetu. V pripade, ze se jedna o jeden prvek, tak je to co sme chteli. Kdyz je to nejaka tovarna, pripadne je tam statickych prvku vic, tak se nactou zbytecne vsechny.
- flexibilita: straci se tim moznost pozdne inicializace, volani kontrukturu s parametry, pripadne jine pripravne prace (neco jde dat do konstruktoru, ale nemusi to byt tak pokazde). Dale v pripade tovarny, nebo potomku jedinacka (zanorena trida, protected konstruktor) neni mozne vracet potomka tridy (zde Helper) misto rodice (zde Helper).
3. Březen 2008 v 10:55
No možná bych podotknul, že ty dva příklady (2,3) se po této úpravě mohou v jistých případech chovat divně. Například při použití nějakého statického prvku třídy se vytvoří instance (nemusela by – nebude to lazy init). Také volání Class.forName(…) vytvoří instanci “dříve”. Jinak – pěkný zápisek.
5. Březen 2008 v 18:40
priklad c.3: casteji jsem videl pouziti staticke vnitrni tridy, potom se inicializace chova korektne a nastastanou v komentarich zminovane problemy:
1. class Helper {
2.
3. public static Helper getHelper() {
4. return HelperHolder.instance;
5. }
6.
7. private static class HelperHolder{
8. static final Helper instance=new Helper();
9. }
10. }
5. Březen 2008 v 18:42
ehm, chtel jsem rici “a NEnastanou v komentarich zminovane problemy”