pátek 22. června 2007

čtvrtek 21. června 2007

Poněkud starší novinky o Springu

V nedávné době se objevilo několik zajímavých článku o Spring frameworku, které mě zaujaly. Všechny články mají společné to, že se netýkají přímo Springu, nicméně se Springem silně souvisí.

Společnost Interface21, která stojí komerčně za Spring frameworkem obdržela na konci května investici v hodnotě 10 milionů dolarů. Tyto peníze by měly sloužít k dalšímu rozvoji Springu. Je v rukou Interface21 jak a na co tyhle peníze použije.

Nad původním Spring frameworkem se postupem času vybudovaly další zajímavé subprojekty jako WebFlow, Acegi Security a další. Za těmito subprojekty stálo zpočátku pár nadšenců, ale postupem času, jak rostla jejich obliba, rostla i komunita kolem nich. Jednou z oblastí, do které by měly přitéci peníze, jsou právě tyto subprojekty a jejich další rozvoj a údržba. Alespoň taková je představa Roda Johnsona v článku Why did we raise $10m?.

Takřka současně se na TheServerSide objevily dvě oznámení ohledně produktů pro clusterování Spring aplikací.

Pokud k tomu připočteme stávající řešení jako Open Terracota a nebo Tangosol Coherence, máme pro aplikace postavené na Springu již solidní podporu pro clustering.

středa 20. června 2007

Synchronizace, JMM a další špeky - díl druhý

V prvním díle našeho povídání jsme si řekli něco málo k změnám paměťového modelu v Jave, který byl představen v rámci verze 5.0. Dnes se trochu blíže podíváme na zoubek tomu, co vězí za novými třídami v package java.util.concurrent. Všechny informace jsem čerpal z článků Briana Goetze, které naleznete na konci textu.

Pokud jste si stáhli Javu 5, možná jste zaznamenali package java.util.concurrent. Tento package obsahuje třídy pro bezpečnou a pohodlnou práci v prostředí multithread aplikací. Možná ovšem nevíte, že většina těchto tříd je implementována bez významného použíti synchronized sekcí. Tím pádem nedochází k tomu, že by thready zbytečně stály a nebo čekaly na zámek. Od Javy 5.0 je totiž možné využít neblokující algoritmy.

CAS

CAS zkratka pro Compare-And-Swap instrukci, která stojí za celým packagem java.util.concurrent, ale pěkně po pořádku. CAS je instrukce, která má tří argumenty paměťové místo M, předpokládanou hodnotu A a novou hodnotu B - CAS(M, A, B). Instrukce udělá to, že se podívá do paměti na místo M, z něj si přečte aktuální hodnotu, kterou porovná s předpokládanou hodnotou A. Pokud jsou stejné, nastaví na místo M novou hodnotu B, pokud se hodnoty nerovnají, neprovede se nic.

To podstatné co zbývá o CAS instrukci říct je, že se jedná o atomickou instrukci. Nemůže tak nastat případ, kdy dvě vlákna paralelně mění jedno paměťové místo. Je tak vyloučeno, že by mohlo dojít k tomu, že se ve vláknu X provede porovnání aktuální a předpokládané hodnoty a před nastavením nové hodnoty by vlákno Y tuto hodnotu změnilo. Sémantika CAS instrukce je v podstatě stejná jako synchronized s tím rozdílem, že je to na úrovni hardwaru namísto JVM. Z toho vyplývá mnohem lepší výkonnost.

Podpora CAS instrukce byla přidána od Javy 5.0 a v API je zpřístupněna v rámci package java.util.concurrent.atomic, kde jsou různá primitiva jako například AtomicInteger či AtomicReference, která modelují CAS instrukci pro různé datové typy. Díky CAS je možné nahradit synchronizaci a to tak, že implementace využije optimistický přístup.

Představme si jednoduchý counter implementovaný pomocí synchronized bloku.

     
publi class Counter {
  private int counter;
  
  public synchronized int inc() {
    return counter++;
  }
}      
     
    

Oproti tomu s využitím AtomicIntegeru.

     
publi class Counter {
  private final AtomicInteger counter = new AtomicInteger();
  
  public int inc() {
     return counter.incrementAndGet();
  }
}      
     
    

Podívejme se na kód metody incrementAndGet (za metodou compareAndSet si představte naší CAS instrukci, metoda get vrací aktuální hodnotu).

    
public final int incrementAndGet() {
  for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next))
    return next;
  }
}    
     
    

Jak je vidět z kódu, pokud se mezi získáním aktuální hodnoty a její inkrementací změní její hodnota dojde znova k provedení celého kolečka. Takhle se pokračuje dokud se danému vláknu nepodaří atomicky inkrementovat hodnotu. Na stejném principu (CAS) jsou založeny i další neblokující algoritmy, které byly použity pro implementaci concurrent tříd.

Změny v Jave 5.0, ať už se jedná o JMM a nebo CAS, umožnily vytvořit optimalizované třídy pro práci v konkurenčním prostředí. Jejich implementace založená na neblokujících algoritmech a nové sémantice volatile nabízí ve většině případů mnohem větší výkon než použití klasického synchronized.

V případě, že budete mít potřebu vytvářet thread safe objekty (mapy, listy apod.), tak doporučuji prozkoumat package java.util.concurrent. Ušetří vám to totiž spoustu času s hledáním případných problémů a bude to mít pozitivní vliv na výkon aplikace.

neděle 17. června 2007

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í metodu writer. 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í, že r2 == 1 není 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í metodu reader vidí resp. pracuje s nacacheovanými hodnotami x,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é slovo synchronized. Synchronized je 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íky synchronized tak 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 slovem volatile. Volatile o proměnné říká, že thread musí vždy pracovat s proměnnou v hlavní paměti. Starý model ovšem pro volatile umožň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émantice volatile je garantováno, že vlákno vidí i předchozí paměťové změny. Volatile má totiž po novu stejné chování (viditelnost, reorder) při práci s pamětí jako synchronized.

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.