SoftEU

Double checked locking

Autor: Petr Ferschmann

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

1 Hvězdička2 Hvězdičky3 Hvězdičky4 Hvězdičky5 Hvězdiček
Načítám ... Načítám ...

7 komentářů k článku “Double checked locking”

  1. Dagi říká:

    Mala poznamka, to bude fungovat az od Javy 1.5 diky rozsisreni semantiky volatile o happens-before.

  2. Ladislav Thon říká:

    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.

  3. Ondrej Svetlik říká:

    No, diky za info, tuhle dvoji kontrolu pouzivam bezne – je fajn se dozvedet, ze to nestaci :-).

  4. kovi říká:

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

  5. lzap říká:

    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.

  6. jw říká:

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

  7. jw říká:

    ehm, chtel jsem rici “a NEnastanou v komentarich zminovane problemy”

Zanechte komentář


Switch to our mobile site