Double checked locking

Peter 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ů.

7 komentářů u „Double checked locking“

  1. 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.

  2. 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. 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.

  4. 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. }

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *