Blog

Blog from October, 2008

Java 6 - Queue és Stack

A Java 6 nyelvi elemekben nem hozott akkora ugrást, mint a Java 5, de jelentősen bővült a felhasználható eszközök száma, sok új osztály került a java.util csomagba, ilyen a Deque osztály is, amelynek a neve "Double Ended QUEue" kifejezésből származik. Ez azt jelenti, hogy a Deque példányt használhatjuk queue és stack üzemmódban is. Csapjunk a közepébe, és nézzük az alábbi programot:

DequeTest.java
import java.util.ArrayDeque;
import java.util.Deque;

public class DequeTest
{
  public static void main(String [] args)
  {
    Deque deque = new ArrayDeque();
    deque.add("Első");
    deque.add("Második");
    deque.add("Középső");
    deque.add("Utolsó előtti");
    deque.add("Utolsó");
    System.out.println(deque.toString());
   
    /**
     * Kiolvassuk az elemet, de nem vesszük ki
     */
    System.out.println(deque.peekFirst());
    System.out.println(deque.peekLast());
       
    /**
     * Kiolvassuk az elemet, és el is távolítjuk
     */
    System.out.println(deque.pollFirst());
    System.out.println(deque.pollLast());
   
    /**
     * Visszatesszük a kivett elemeket
     */
    deque.push("Első");
    deque.add("Utolsó");   
    System.out.println(deque.toString());
  }
}

Ahogy a programból látszik: egyszerűen hozzá tudunk adni elemeket a listához, amelynek elejéről és végéről is közel azonos módon tudunk elemeket kiolvasni, kivenni illetve beletenni. A program kimenete:

[Első, Második, Középső, Utolsó előtti, Utolsó]
Első
Utolsó
Első
Utolsó
[Első, Második, Középső, Utolsó előtti, Utolsó]

Az osztály több metódussal is rendelkezik a fentieken kívül, amelyek alapvetően rövidebb nevet jelentenek a gyakran használt műveletekhez, másrészt pedig a metódusok egy csoportjánál NoSuchElementException váltódik ki, ha üres a lista.

ThreadInfo lekérdezése

A Java 6 megjelenésével több belső információhoz férhetünk hozzá futásidőben, amelyek közül érdekes lehet lekérdezni a szálak adatait, beleértve olyan metrikát, mint a felhasznált CPU idő. A titok a ManagementFactory statikus metódusai körül van, innen tudjuk lekérdezni a ThreadMXBean példányt, amelyből le tudjuk kérdezni az éppen létező szálak adatait:

    ThreadMXBean threads = ManagementFactory.getThreadMXBean();
    ThreadInfo[] threadInfos = threads.getThreadInfo(threads.getAllThreadIds());
    for (int i = 0; i < threadInfos.length; i++)
    {
      long nanoTime = threads.getThreadCpuTime(threadInfos[i].getThreadId());
      System.out.println("cpuTime[" + threadInfos[i].getThreadName() + "]: " + nanoTime);
    }

A CPU időn túl több metrika is felderíthető, érdemes megtekinteni a ThreadMXBean illetve a ThreadInfo metódusait.

Gyenge referencia

A java egy régi (1.2-es verziója óta létező) de kevéssé ismert részéhez tartozik a gyenge referencia. Igaz alkalmazási területe is elég szűk, de ha tudjuk, hogy léteznek és mire jók, a megfelelő időben előhúzva a zsebünkből nagyon hasznos tud lenni a java.lang.ref csomag. Ha egy mondatban össze kellene foglalni mi is az a gyenge referencia akkor úgy írnám le, hogy olyan hivatkozás egy objektumra, ami nem akadályozza meg a szemétgyűjtőt (garbage collector), hogy a hivatkozott objektumot kitakarítsa a memóriából. Ahhoz, hogy pontosan megértsük hogy miről is van szó, egy kicsit elő kell vennünk a szemétgyűjtésről tanultakat.

Az alapoknál kezdve, a java nyelvben nem kell foglalkoznunk az objektumok memóriaterületének felszabadításával, a JVM szemétgyűjtője elvégzi ezt helyettünk minden olyan objektumra, ami már nem elérhető az aktuális programfutási szakaszból. Egész pontosan erős referenciával nem elérhető. Nézzünk erős referenciára egy példát:

String süti = new String("kókuszkocka");

Itt a süti egy erős referencia egy String objektumra. Ha a süti kikerül a láthatósági körünkből, vagy null értéket adunk neki (most nem vagyunk éhesek), akkor a szemétgyűjtő bármikor begyűjtheti azt. Fontos azonban, hogy a szemétgyűjtő lusta és nem azonnal szabadítja fel a memóriát, lehet hogy csak akkor szabadítja fel, amikor teljesen elfogy a JVM rendelkezésére álló memória (megtelik a hűtő). Próbáljuk meg visszaszerezni a sütinket, amíg nem késő...

A java.lang.ref csomag háromféle referenciát ad a kezünkbe, ezek erősségi sorrendben a következők:

  • puha (soft)
  • gyenge (weak)
  • fantom (phantom).

Gyenge referencia

Nézzük meg először a gyenge referenciát. Ha ezzel a referenciával hivatkozunk egy objektumra, és az erős referenciákon keresztül már nem elérhető, akkor azt a szemétgyűjtő bármikor megsemmisítheti, de addig amíg ez meg nem történik mi is elérhetjük, és visszaállíthatjuk. Nézzük meg példával:

WeakReference<String> sütiemlék = new WeakReference<String>(süti);
süti = null;

Ekkor - amíg a szemétgyűjtő ki nem söpri a memóriát - a sütiemlék.get() utasítással még elérhetjük a sütinket. Természetesen ekkor beállíthatunk egy (erős) referenciát a sütire, így kiragadva a sütit a GC kezéből. Ha a sütink már megsemmisült null értéket kapunk vissza a get() hívásra:

süti = sütiemlék.get();
if (süti!=null)
   System.out.println(süti);
else
   System.out.println("valaki már megette");
Szép-szép, de a valós életben mire jó ez?
  • Gyorstárak létrehozására: már betöltött de ideiglenes nem kellő nagy méretű fájlokat (pl. képeket) újra előhozhatunk a memóriából, és nem kell újra felolvasnunk azokat a lemezről.
  • Olyan osztályokhoz amiket nem tudunk kiterjeszteni, pl. egy factory-tól kapott, egy adott interfészt megvalósító osztályokhoz rendelhetünk további attribútumokat, úgy hogy ezzel nem tartjuk bent az eredeti objektumokat a memóriában. Ehhez ad további segítséget a weakHashMap amit később tárgyalunk.

Puha referencia

A puha (Soft) referencia hasonlóan működik mint a gyenge referencia, a különbségük mindössze abban rejlik, hogy a puha referencia egyel erősebb kötelék, azaz a GC megpróbálja minél tovább benntartani a hivatkozott objektumot a memóriában. Persze garancia ugyanúgy nincs rá, hogy még megtaláljuk, amikor kell nekünk, de gyorstárak megvalósításához ez a referencia ajánlott. Abban viszont biztosak lehetünk, hogy a program memóriaigényét az így megvalósított cache nem korlátozza, mivel a GC kitakarít minden olyan objektumot amire csak puha referencia hivatkozik mielőtt OutOfMemoryException-t dobna.

Referencia sorok

Ha egy WeakReference objektum egy már megsemmisített objektumra hivatkozik akkor már igazából ő maga is fölöslegessé válik. Azaz a programunknak időről időre érdemes kitakarítania a nem funkcionáló gyenge referenciákat. Ehhez nyújt segítséget a ReferenceQueue. Ha egy gyenge referenciának a konstruktorában megadunk egy ReferenceQueue-t akkor miután az általa hivatkozott objektum elérhetetlenné válik, belekerül a queue-ba. Igazából a WeakHashMap is azon az elven működik, hogy a kulcs elérése előtt leellenőrzi a saját ReferenceQueue-jában hogy nincs-e érvénytelen gyenge referencia, ha van akkor eltávolítja azt.

Fantom referencia

Végül a fantom referencia segítségével a java finalize-t tudjuk „kiterjeszteni”. Segítségével pl. külső erőforrás felszabadításokat végezhetünk . A fantom referencia konstruktorában mindig meg kell adni egy referencia sort. A referencia általa hivatkozott objektum finalize metódusa után, de a memóriából tényleges kikerülése előtt kerül be ebbe a megadott sorba. Ekkor az objektum már törlésre van ítélve és azt nem is lehet visszaállítani, ezt biztosítva a phantomReference get függvénye mindig null értékkel tér vissza. A tényleges törlés azonban csak akkor történik meg ha a phantom reference-nek a clear metódusát meghívjuk. Összefoglalva, ha egy fantom referencia által hivatkozott objektumot a GC fel akar szabadítani akkor meghívja az objektumra a finalize metódust, majd behelyezi a fantom referenciát a megadott referencia sorba, de magát az objektumot csak akkor tudja felszabadítani ha a fantom referenciának meghívják a clear metódusát..

WeakHashMap

A WeakHashMap egy gyenge referenciákat tartalmazó hasznos kis HashMap, azaz a kulcs-érték párból a kulcs egy gyenge referencia, így nem tartja a memóriában a hivatkozott objektumokat. Továbbá kényelmes a használata, mert adatszerkezet automatikusan kitakarítja magából a nem létező hivatkozásokat, azaz ha a kulcs által hivatkozott objektum megszűnik akkor a WeakHashMap-ből a kulcs-érték pár is kitörlődik. Használatánál oda kell figyelni arra, hogy bármikor eltűnhetnek belőle kulcs-érték párok, hisz a GC bármikor takaríthat, illetve a kulcs alapján való érték visszakeresés se triviális minden esetben, hisz ha van egy erős referenciánk a kulcsra, akkor nincs sok értelme az egésznek.

Objektum elérhetősége

A különböző objektumok elérhetőségét a rá hivatkozott referenciák erőssége szerint csoportba tudjuk sorolni. A következő lista felsorolja a különböző elérhetőségi szinteket legerősebbtől kezdve a leggyengébbig:

  • erősen elérhető egy objektum ha hivatkozik rá egy referencia.
  • Puhán (softly) elérhető egy objektum ha nem erősen elérhető de hivatkozik rá egy puha referencia
  • Gyengén elérhető egy objektum ha se nem erősen se nem puhán nem elérhető de hivatkozik rá egy gyenge referencia
  • Fantom elérhető egy objektum ha se nem erősen se nem puhán sem gyengén nem elérhető de hivatkozik rá egy fantom referencia.
  • Nem elérhető egy objektum ha a fenti listában egyik kapcsolódási típus sem igaz rá.

Az idevonatkozó Java API: http://java.sun.com/javase/6/docs/api/java/lang/ref/package-summary.html