Automatická správa zdrojů

V C++ existuje jedna velmi používaná technika pro získávání/uvolňování zdroje (zamykání, alokaci paměti, vrácení databázového spojení do connection poolu, apod.) – používají ji např. autopointery (auto_ptr<> a Smart Pointers z boost.org).

Základní myšlenkou je, že vytvoříte jednoduchý objekt na zásobníku. V jeho konstruktoru získáte zdroj a v destruktoru opět uvolníte. Celé je to velmi efektivní, protože dojde k alokaci paměti jen na zásobníku.

Kód vypadá takto:

if (true) { 
        auto_ptr data(new Data());
        data->delej();
}
// zde jsou data již uvolněna.

Velkou výhodou tohoto způsobu je, že se nikdy nemusíte bát, zda jste zapomněli uvolnit zdroj. Překladač to za vás udělá sám. A při nahlédnutí do kódu vždy víte, že k uvolnění dojde. Toto je také jediný způsob jak bezpečně pracovat s pamětí v konstruktoru v C++ (viz. Exceptional C++).

Knihovna boost toto rozšiřuje i na reference counting „ukazatele“.

Vraťme se nyní k Javě. Přímá aplikace myšlenky nefunguje – v Javě není definováno kdy bude volán finalizer (obdoba destruktoru) a zda bude volán vůbec.
Fakt, že uvolňování zdroje je velmi důležité, ukazuje i příkaz synchronized:

synchronized (this) {
 // pracuj
}

Možnou náhradou tedy je ruční volání s použitím finally

try {
        resource.lock();

       // pracuj
} finally {
        resource.unlock();
} 

Oproti C++ variantě, ale můžeme něco přehlédnout, udělat chybu a na uvolnění prostě zapomenout. Příčinu problému pak můžeme hledat dlouho. Kolikrát jste hledali, kde se neuvolňuje spojení do databáze?

Nakonec nám tedy zbývá toto:

Resource.lock(resource, new Action() {
        public void run() {
                 // pracuj
        }
});

Před voláním metodu Action.run získáme zdroj a později opět uvolníme. Toto funkčností odpovídá variantě v C++ asi nejvíce, ale není tak elegantní a jednoduché.

Teď nám zbývá už jen doufat, že budoucí verze Javy přinese v tomto směru nějaké zlepšení. S podporou closures by kód mohl vypadat nějak takto:

Resource.lock(resource, {
  // pracuj
});

nebo také:

protected (Connection = getConnection()) {

      // Connection implements Closable, so it's automatically closed at the end of this block

      // pracuj

 }

Pro tyto účely by byla hezké použití zpětné analýzy (escape analysis) pro alokaci paměti (v Javě 6 je již pro zamykání).

Na druhou stranu musím říct, že problém uvolňování zdrojů díky garbage collectoru a synchronized v Javě nenastává vůbec tak často. A těch několik míst, kde se získává spojení, si snadno ohlídáme.

Napsat komentář

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